commit
56efd70c53
|
@ -430,7 +430,7 @@ class account_bank_statement(osv.osv):
|
|||
'name': st_number,
|
||||
'balance_end_real': st.balance_end
|
||||
}, context=context)
|
||||
self.message_append_note(cr, uid, [st.id], body=_('Statement %s is confirmed, journal items are created.') % (st_number,), context=context)
|
||||
self.message_post(cr, uid, [st.id], body=_('Statement %s confirmed, journal items were created.') % (st_number,), context=context)
|
||||
return self.write(cr, uid, ids, {'state':'confirm'}, context=context)
|
||||
|
||||
def button_cancel(self, cr, uid, ids, context=None):
|
||||
|
|
|
@ -1045,7 +1045,7 @@ class account_invoice(osv.osv):
|
|||
if obj_inv.type in ('out_invoice', 'out_refund'):
|
||||
ctx = self.get_log_context(cr, uid, context=ctx)
|
||||
message = _("Invoice '%s' is validated.") % name
|
||||
self.message_append_note(cr, uid, [inv_id], body=message, context=context)
|
||||
self.message_post(cr, uid, [inv_id], body=message, context=context)
|
||||
return True
|
||||
|
||||
def action_cancel(self, cr, uid, ids, *args):
|
||||
|
@ -1275,7 +1275,7 @@ class account_invoice(osv.osv):
|
|||
# TODO: use currency's formatting function
|
||||
msg = _("Invoice '%s' is paid partially: %s%s of %s%s (%s%s remaining).") % \
|
||||
(name, pay_amount, code, invoice.amount_total, code, total, code)
|
||||
self.message_append_note(cr, uid, [inv_id], body=msg, context=context)
|
||||
self.message_post(cr, uid, [inv_id], body=msg, context=context)
|
||||
self.pool.get('account.move.line').reconcile_partial(cr, uid, line_ids, 'manual', context)
|
||||
|
||||
# Update the stored value (fields.function), so we write to trigger recompute
|
||||
|
@ -1288,24 +1288,25 @@ class account_invoice(osv.osv):
|
|||
|
||||
def _get_document_type(self, type):
|
||||
type_dict = {
|
||||
'out_invoice': 'Customer invoice',
|
||||
'in_invoice': 'Supplier invoice',
|
||||
'out_refund': 'Customer Refund',
|
||||
'in_refund': 'Supplier Refund',
|
||||
# Translation markers will have no effect at runtime, only used to properly flag export
|
||||
'out_invoice': _('Customer invoice'),
|
||||
'in_invoice': _('Supplier invoice'),
|
||||
'out_refund': _('Customer Refund'),
|
||||
'in_refund': _('Supplier Refund'),
|
||||
}
|
||||
return type_dict.get(type, 'Invoice')
|
||||
|
||||
def create_send_note(self, cr, uid, ids, context=None):
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
self.message_append_note(cr, uid, [obj.id],body=_("%s <b>created</b>.") % (self._get_document_type(obj.type)), context=context)
|
||||
self.message_post(cr, uid, [obj.id], body=_("%s <b>created</b>.") % (_(self._get_document_type(obj.type))), context=context)
|
||||
|
||||
def confirm_paid_send_note(self, cr, uid, ids, context=None):
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
self.message_append_note(cr, uid, [obj.id], body=_("%s <b>paid</b>.") % (self._get_document_type(obj.type)), context=context)
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
self.message_post(cr, uid, [obj.id], body=_("%s <b>paid</b>.") % (_(self._get_document_type(obj.type))), context=context)
|
||||
|
||||
def invoice_cancel_send_note(self, cr, uid, ids, context=None):
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
self.message_append_note(cr, uid, [obj.id], body=_("%s <b>cancelled</b>.") % (self._get_document_type(obj.type)), context=context)
|
||||
self.message_post(cr, uid, [obj.id], body=_("%s <b>cancelled</b>.") % (_(self._get_document_type(obj.type))), context=context)
|
||||
|
||||
account_invoice()
|
||||
|
||||
|
|
|
@ -123,64 +123,6 @@
|
|||
</div>
|
||||
</div>
|
||||
]]></field>
|
||||
<field name="body_text"><![CDATA[
|
||||
Hello${object.partner_id.name and ' ' or ''}${object.partner_id.name or ''},
|
||||
|
||||
A new invoice is available for ${object.partner_id.name}:
|
||||
| Invoice number: *${object.number}*
|
||||
| Invoice total: *${object.amount_total} ${object.currency_id.name}*
|
||||
| Invoice date: ${object.date_invoice}
|
||||
% if object.origin:
|
||||
| Order reference: ${object.origin}
|
||||
% endif
|
||||
| Your contact: ${object.user_id.name} ${object.user_id.email and '<%s>'%(object.user_id.email) or ''}
|
||||
|
||||
You can view the invoice document, download it and pay online using the following link:
|
||||
${ctx.get('edi_web_url_view') or 'n/a'}
|
||||
|
||||
% if object.company_id.paypal_account and object.type in ('out_invoice', 'in_refund'):
|
||||
<%
|
||||
comp_name = quote(object.company_id.name)
|
||||
inv_number = quote(object.number)
|
||||
paypal_account = quote(object.company_id.paypal_account)
|
||||
inv_amount = quote(str(object.amount_total))
|
||||
cur_name = quote(object.currency_id.name)
|
||||
paypal_url = "https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=%s&item_name=%s%%20Invoice%%20%s"\
|
||||
"&invoice=%s&amount=%s¤cy_code=%s&button_subtype=services&no_note=1&bn=OpenERP_Invoice_PayNow_%s" % \
|
||||
(paypal_account,comp_name,inv_number,inv_number,inv_amount,cur_name,cur_name)
|
||||
%>
|
||||
It is also possible to directly pay with Paypal:
|
||||
${paypal_url}
|
||||
% endif
|
||||
|
||||
If you have any question, do not hesitate to contact us.
|
||||
|
||||
|
||||
Thank you for choosing ${object.company_id.name}!
|
||||
|
||||
|
||||
--
|
||||
${object.user_id.name} ${object.user_id.email and '<%s>'%(object.user_id.email) or ''}
|
||||
${object.company_id.name}
|
||||
% if object.company_id.street:
|
||||
${object.company_id.street or ''}
|
||||
% endif
|
||||
% if object.company_id.street2:
|
||||
${object.company_id.street2}
|
||||
% endif
|
||||
% if object.company_id.city or object.company_id.zip:
|
||||
${object.company_id.zip or ''} ${object.company_id.city or ''}
|
||||
% endif
|
||||
% if object.company_id.country_id:
|
||||
${object.company_id.state_id and ('%s, ' % object.company_id.state_id.name) or ''} ${object.company_id.country_id.name or ''}
|
||||
% endif
|
||||
% if object.company_id.phone:
|
||||
Phone: ${object.company_id.phone}
|
||||
% endif
|
||||
% if object.company_id.website:
|
||||
${object.company_id.website or ''}
|
||||
% endif
|
||||
]]></field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -9538,7 +9538,7 @@ msgid "Refund"
|
|||
msgstr ""
|
||||
|
||||
#. module: account
|
||||
#: model:email.template,body_text:account.email_template_edi_invoice
|
||||
#: model:email.template,body:account.email_template_edi_invoice
|
||||
msgid "\n"
|
||||
"Hello${object.address_invoice_id.name and ' ' or ''}${object.address_invoice_id.name or ''},\n"
|
||||
"\n"
|
||||
|
|
|
@ -21,13 +21,14 @@
|
|||
</record>
|
||||
|
||||
<!-- Notify all employees of module installation -->
|
||||
<function model="mail.group" name="message_append_note">
|
||||
<!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
|
||||
<value eval="[ref('mail.group_all_employees')]"/>
|
||||
<value>Module Accounting and Finance has been installed.</value>
|
||||
<value>With OpenERP's accounting, you can get an instant access to all your financial data, setup your analytic accounting, forecast your taxes, control your budgets, easily create and send invoices, record bank statements, etc.
|
||||
<record model="mail.message" id="module_install_notification">
|
||||
<field name="model">mail.group</field>
|
||||
<field name="res_id" ref="mail.group_all_employees"/>
|
||||
<field name="type">notification</field>
|
||||
<field name="subject">Accounting and Finance application installed!</field>
|
||||
<field name="body">With OpenERP's accounting, you get instant access to your financial data, and can setup analytic accounting, forecast taxes, control budgets, easily create and send invoices, record bank statements, etc.
|
||||
|
||||
The accounting features are fully integrated with others OpenERP applications to automate all your processes: creation of customer invoices, control of supplier invoices, point-of-sale integration, automated follow-ups, etc.</value>
|
||||
</function>
|
||||
The accounting features are fully integrated with other OpenERP applications to automate all your processes: creation of customer invoices, control of supplier invoices, point-of-sale integration, automated follow-ups, etc.</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -213,8 +213,6 @@ class account_followup_print_all(osv.osv_memory):
|
|||
mod_obj = self.pool.get('ir.model.data')
|
||||
move_obj = self.pool.get('account.move.line')
|
||||
user_obj = self.pool.get('res.users')
|
||||
line_obj = self.pool.get('account_followup.stat')
|
||||
mail_message = self.pool.get('mail.message')
|
||||
|
||||
if context is None:
|
||||
context = {}
|
||||
|
@ -235,13 +233,7 @@ class account_followup_print_all(osv.osv_memory):
|
|||
total_amt += line.debit - line.credit
|
||||
dest = False
|
||||
if partner:
|
||||
if partner.type=='contact':
|
||||
if adr.email:
|
||||
dest = [partner.email]
|
||||
if (not dest) and partner.type=='default':
|
||||
if partner.email:
|
||||
dest = [partner.email]
|
||||
src = tools.config.options['email_from']
|
||||
dest = [partner.email]
|
||||
if not data.partner_lang:
|
||||
body = data.email_body
|
||||
else:
|
||||
|
@ -281,7 +273,12 @@ class account_followup_print_all(osv.osv_memory):
|
|||
msg = ''
|
||||
if dest:
|
||||
try:
|
||||
mail_message.schedule_with_attach(cr, uid, src, dest, sub, body, context=context)
|
||||
vals = {'state': 'outgoing',
|
||||
'subject': sub,
|
||||
'body_html': '<pre>%s</pre>' % body,
|
||||
'email_to': dest,
|
||||
'email_from': data_user.email or tools.config.options['email_from']}
|
||||
self.pool.get('mail.mail').create(cr, uid, vals, context=context)
|
||||
msg_sent += partner.name + '\n'
|
||||
except Exception, e:
|
||||
raise osv.except_osv('Error !', e )
|
||||
|
|
|
@ -1293,17 +1293,17 @@ class account_voucher(osv.osv):
|
|||
def create_send_note(self, cr, uid, ids, context=None):
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
message = "%s <b>created</b>." % self._document_type[obj.type or False]
|
||||
self.message_append_note(cr, uid, [obj.id], body=message, context=context)
|
||||
self.message_post(cr, uid, [obj.id], body=message, context=context)
|
||||
|
||||
def post_send_note(self, cr, uid, ids, context=None):
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
message = "%s '%s' is <b>posted</b>." % (self._document_type[obj.type or False], obj.move_id.name)
|
||||
self.message_append_note(cr, uid, [obj.id], body=message, context=context)
|
||||
self.message_post(cr, uid, [obj.id], body=message, context=context)
|
||||
|
||||
def reconcile_send_note(self, cr, uid, ids, context=None):
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
message = "%s <b>reconciled</b>." % self._document_type[obj.type or False]
|
||||
self.message_append_note(cr, uid, [obj.id], body=message, context=context)
|
||||
self.message_post(cr, uid, [obj.id], body=message, context=context)
|
||||
|
||||
account_voucher()
|
||||
|
||||
|
|
|
@ -2,15 +2,16 @@
|
|||
<openerp>
|
||||
<data noupdate="1">
|
||||
<!-- notify all employees of module installation -->
|
||||
<function model="mail.group" name="message_append_note">
|
||||
<!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
|
||||
<value eval="[ref('mail.group_all_employees')]"/>
|
||||
<value>Module eInvoicing & Payments has been installed.</value>
|
||||
<value>OpenERP's electronic invoicing allows to ease and fasten the creation of invoices and collection of customer payments. Invoices are created in a few clicks and your customers receive them by email. They can pay online and/or import them in their own system.
|
||||
<record model="mail.message" id="module_install_notification">
|
||||
<field name="model">mail.group</field>
|
||||
<field name="res_id" ref="mail.group_all_employees"/>
|
||||
<field name="type">notification</field>
|
||||
<field name="subject">eInvoicing & Payments application installed!</field>
|
||||
<field name="body">OpenERP's electronic invoicing accelerates the creation of invoices and collection of customer payments. Invoices are created in a few clicks and your customers receive them by email. They can pay online and/or import them in their own system.
|
||||
|
||||
You can track customer payments easily and automate the reminders. You get an overview of the discussion with your customers on each invoice to ensure a full traceability.
|
||||
You can track customer payments easily and automate follow-ups. You get an overview of the discussion with your customers on each invoice for easier traceability.
|
||||
|
||||
If you want to use advanced accounting features, you should install the "Accounting and Finance" module.</value>
|
||||
</function>
|
||||
For advanced accounting features, you should install the "Accounting and Finance" module.</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -297,7 +297,7 @@ class account_analytic_account(osv.osv):
|
|||
|
||||
def create_send_note(self, cr, uid, ids, context=None):
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
self.message_append_note(cr, uid, [obj.id], body=_("Contract for <em>%s</em> has been <b>created</b>.") % (obj.partner_id.name), context=context)
|
||||
self.message_post(cr, uid, [obj.id], body=_("Contract for <em>%s</em> has been <b>created</b>.") % (obj.partner_id.name), context=context)
|
||||
|
||||
account_analytic_account()
|
||||
|
||||
|
|
|
@ -9,16 +9,13 @@
|
|||
<field name="email_from"><![CDATA[${object.company_id.name} <${object.company_id.email}>]]></field>
|
||||
<field name="email_to" eval="False"><!--(set by reset_password module)--></field>
|
||||
<field name="subject">Password reset</field>
|
||||
<field name="body_text"><![CDATA[
|
||||
A password reset was requested the OpenERP account linked to this email on ${object._auth_reset_password_host()}
|
||||
<field name="body_html"><![CDATA[
|
||||
<p>A password reset was requested the OpenERP account linked to this email on ${object._auth_reset_password_host()}</p>
|
||||
|
||||
You may change your password following this link:
|
||||
<p>You may change your password following this <a href="${object._auth_reset_password_link()}">link</a>,
|
||||
or by copy-pasting the following URL in your browser: ${object._auth_reset_password_link()}</p>
|
||||
|
||||
${object._auth_reset_password_link()}
|
||||
|
||||
If you don't have asked for password reset, you can safely ignore this email.
|
||||
|
||||
]]></field>
|
||||
<p>Note: If you did not ask for a password reset, you can safely ignore this email.</p>]]></field>
|
||||
</record>
|
||||
|
||||
<!-- TODO get own css -->
|
||||
|
|
|
@ -302,33 +302,27 @@ the rule to mark CC(mail to any other person defined in actions)."),
|
|||
return self.format_body(body % data)
|
||||
|
||||
def email_send(self, cr, uid, obj, emails, body, emailfrom=None, context=None):
|
||||
""" send email
|
||||
@param self: The object pointer
|
||||
@param cr: the current row, from the database cursor,
|
||||
@param uid: the current user’s ID for security checks,
|
||||
@param email: pass the emails
|
||||
@param emailfrom: Pass name the email From else False
|
||||
@param context: A standard dictionary for contextual values """
|
||||
|
||||
if not emailfrom:
|
||||
emailfrom = tools.config.get('email_from', False)
|
||||
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
mail_message = self.pool.get('mail.message')
|
||||
emailfrom = tools.config.get('email_from')
|
||||
body = self.format_mail(obj, body)
|
||||
if not emailfrom:
|
||||
if hasattr(obj, 'user_id') and obj.user_id and obj.user_id.email:
|
||||
emailfrom = obj.user_id.email
|
||||
|
||||
name = '[%d] %s' % (obj.id, tools.ustr(obj.name))
|
||||
if not emailfrom and hasattr(obj, 'user_id') and obj.user_id and obj.user_id.email:
|
||||
emailfrom = obj.user_id.email
|
||||
emailfrom = tools.ustr(emailfrom)
|
||||
reply_to = emailfrom
|
||||
if not emailfrom:
|
||||
raise osv.except_osv(_('Error!'),
|
||||
_("No email ID found for your company address."))
|
||||
return mail_message.schedule_with_attach(cr, uid, emailfrom, emails, name, body, model='base.action.rule', reply_to=reply_to, res_id=obj.id)
|
||||
_("Missing default email address or missing email on responsible user"))
|
||||
return self.pool.get('mail.mail').create(cr, uid,
|
||||
{ 'email_from': emailfrom,
|
||||
'email_to': emails.join(','),
|
||||
'reply_to': reply_to,
|
||||
'state': 'outgoing',
|
||||
'subject': '[%d] %s' % (obj.id, tools.ustr(obj.name)),
|
||||
'body_html': '<pre>%s</pre>' % body,
|
||||
'res_id': obj.id,
|
||||
'model': obj._table_name,
|
||||
'auto_delete': True
|
||||
}, context=context)
|
||||
|
||||
|
||||
def do_check(self, cr, uid, action, obj, context=None):
|
||||
|
@ -438,11 +432,8 @@ the rule to mark CC(mail to any other person defined in actions)."),
|
|||
if len(emails) and action.act_mail_body:
|
||||
emails = list(set(emails))
|
||||
email_from = safe_eval(action.act_email_from, {}, locals_for_emails)
|
||||
|
||||
def to_email(text):
|
||||
return re.findall(r'([^ ,<@]+@[^> ,]+)', text or '')
|
||||
emails = to_email(','.join(filter(None, emails)))
|
||||
email_froms = to_email(email_from)
|
||||
emails = tools.email_split(','.join(filter(None, emails)))
|
||||
email_froms = tools.email_split(email_from)
|
||||
if email_froms:
|
||||
self.email_send(cr, uid, obj, emails, action.act_mail_body, emailfrom=email_froms[0])
|
||||
return True
|
||||
|
|
|
@ -471,18 +471,10 @@ property or property parameter."),
|
|||
def _send_mail(self, cr, uid, ids, mail_to, email_from=tools.config.get('email_from', False), context=None):
|
||||
"""
|
||||
Send mail for event invitation to event attendees.
|
||||
@param cr: the current row, from the database cursor,
|
||||
@param uid: the current user’s ID for security checks,
|
||||
@param ids: List of attendee’s IDs.
|
||||
@param email_from: Email address for user sending the mail
|
||||
@param context: A standard dictionary for contextual values
|
||||
@return: True
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.name
|
||||
mail_message = self.pool.get('mail.message')
|
||||
for att in self.browse(cr, uid, ids, context=context):
|
||||
sign = att.sent_by_uid and att.sent_by_uid.signature or ''
|
||||
sign = '<br>'.join(sign and sign.split('\n') or [])
|
||||
|
@ -508,17 +500,18 @@ property or property parameter."),
|
|||
}
|
||||
body = html_invitation % body_vals
|
||||
if mail_to and email_from:
|
||||
attach = self.get_ics_file(cr, uid, res_obj, context=context)
|
||||
mail_message.schedule_with_attach(cr, uid,
|
||||
email_from,
|
||||
mail_to,
|
||||
sub,
|
||||
body,
|
||||
attachments=attach and {'invitation.ics': attach} or None,
|
||||
content_subtype='html',
|
||||
reply_to=email_from,
|
||||
context=context
|
||||
)
|
||||
ics_file = self.get_ics_file(cr, uid, res_obj, context=context)
|
||||
vals = {'email_from': email_from,
|
||||
'email_to': mail_to,
|
||||
'state': 'outgoing',
|
||||
'subject': sub,
|
||||
'body_html': body,
|
||||
'auto_delete': True}
|
||||
if ics_file:
|
||||
vals['attachment_ids'] = [(0,0,{'name': 'invitation.ics',
|
||||
'datas_fname': 'invitation.ics',
|
||||
'datas': str(ics_file).encode('base64')})]
|
||||
self.pool.get('mail.mail').create(cr, uid, vals, context=context)
|
||||
return True
|
||||
|
||||
def onchange_user_id(self, cr, uid, ids, user_id, *args, **argv):
|
||||
|
@ -812,7 +805,6 @@ class calendar_alarm(osv.osv):
|
|||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
mail_message = self.pool.get('mail.message')
|
||||
current_datetime = datetime.now()
|
||||
alarm_ids = self.search(cr, uid, [('state', '!=', 'done')], context=context)
|
||||
|
||||
|
@ -849,36 +841,10 @@ class calendar_alarm(osv.osv):
|
|||
else:
|
||||
re_dates = [alarm.trigger_date]
|
||||
|
||||
for r_date in re_dates:
|
||||
ref = alarm.model_id.model + ',' + str(alarm.res_id)
|
||||
|
||||
# search for alreay sent requests
|
||||
#if request_obj.search(cr, uid, [('trigger_date', '=', r_date), ('ref_doc1', '=', ref)], context=context):
|
||||
#continue
|
||||
|
||||
# Deactivated because of the removing of res.request
|
||||
# TODO: when cleaning calendar module, re-add this in a new mechanism
|
||||
#if alarm.action == 'display':
|
||||
#value = {
|
||||
#'name': alarm.name,
|
||||
#'act_from': alarm.user_id.id,
|
||||
#'act_to': alarm.user_id.id,
|
||||
#'body': alarm.description,
|
||||
#'trigger_date': r_date,
|
||||
#'ref_doc1': ref
|
||||
#}
|
||||
#request_id = request_obj.create(cr, uid, value)
|
||||
#request_ids = [request_id]
|
||||
#for attendee in res_obj.attendee_ids:
|
||||
#if attendee.user_id:
|
||||
#value['act_to'] = attendee.user_id.id
|
||||
#request_id = request_obj.create(cr, uid, value)
|
||||
#request_ids.append(request_id)
|
||||
#request_obj.request_send(cr, uid, request_ids)
|
||||
|
||||
if re_dates:
|
||||
if alarm.action == 'email':
|
||||
sub = '[Openobject Reminder] %s' % (alarm.name)
|
||||
body = """
|
||||
sub = '[OpenERP Reminder] %s' % (alarm.name)
|
||||
body = """<pre>
|
||||
Event: %s
|
||||
Event Date: %s
|
||||
Description: %s
|
||||
|
@ -888,20 +854,21 @@ From:
|
|||
|
||||
----
|
||||
%s
|
||||
|
||||
</pre>
|
||||
""" % (alarm.name, alarm.trigger_date, alarm.description, \
|
||||
alarm.user_id.name, alarm.user_id.signature)
|
||||
mail_to = [alarm.user_id.email]
|
||||
for att in alarm.attendee_ids:
|
||||
mail_to.append(att.user_id.email)
|
||||
if mail_to:
|
||||
mail_message.schedule_with_attach(cr, uid,
|
||||
tools.config.get('email_from', False),
|
||||
mail_to,
|
||||
sub,
|
||||
body,
|
||||
context=context
|
||||
)
|
||||
vals = {
|
||||
'state': 'outgoing',
|
||||
'subject': sub,
|
||||
'body_html': body,
|
||||
'email_to': mail_to,
|
||||
'email_from': tools.config.get('email_from', mail_to),
|
||||
}
|
||||
self.pool.get('mail.mail').create(cr, uid, vals, context=context)
|
||||
if next_trigger_date:
|
||||
update_vals.update({'trigger_date': next_trigger_date})
|
||||
else:
|
||||
|
@ -1616,36 +1583,6 @@ class calendar_todo(osv.osv):
|
|||
|
||||
calendar_todo()
|
||||
|
||||
class ir_attachment(osv.osv):
|
||||
_name = 'ir.attachment'
|
||||
_inherit = 'ir.attachment'
|
||||
|
||||
def search_count(self, cr, user, args, context=None):
|
||||
new_args = []
|
||||
for domain_item in args:
|
||||
if isinstance(domain_item, (list, tuple)) and len(domain_item) == 3 and domain_item[0] == 'res_id':
|
||||
new_args.append((domain_item[0], domain_item[1], base_calendar_id2real_id(domain_item[2])))
|
||||
else:
|
||||
new_args.append(domain_item)
|
||||
return super(ir_attachment, self).search_count(cr, user, new_args, context)
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
if context:
|
||||
id = context.get('default_res_id', False)
|
||||
context.update({'default_res_id' : base_calendar_id2real_id(id)})
|
||||
return super(ir_attachment, self).create(cr, uid, vals, context=context)
|
||||
|
||||
def search(self, cr, uid, args, offset=0, limit=None, order=None,
|
||||
context=None, count=False):
|
||||
new_args = []
|
||||
for domain_item in args:
|
||||
if isinstance(domain_item, (list, tuple)) and len(domain_item) == 3 and domain_item[0] == 'res_id':
|
||||
new_args.append((domain_item[0], domain_item[1], base_calendar_id2real_id(domain_item[2])))
|
||||
else:
|
||||
new_args.append(domain_item)
|
||||
return super(ir_attachment, self).search(cr, uid, new_args, offset=offset,
|
||||
limit=limit, order=order, context=context, count=False)
|
||||
ir_attachment()
|
||||
|
||||
class ir_values(osv.osv):
|
||||
_inherit = 'ir.values'
|
||||
|
|
|
@ -43,7 +43,7 @@ class crm_meeting(base_state, osv.Model):
|
|||
_name = 'crm.meeting'
|
||||
_description = "Meeting"
|
||||
_order = "id desc"
|
||||
_inherit = ["calendar.event", 'ir.needaction_mixin', "mail.thread"]
|
||||
_inherit = ["calendar.event", "mail.thread", 'ir.needaction_mixin']
|
||||
_columns = {
|
||||
# base_state required fields
|
||||
'create_date': fields.datetime('Creation Date', readonly=True),
|
||||
|
@ -70,13 +70,17 @@ class crm_meeting(base_state, osv.Model):
|
|||
# OpenChatter
|
||||
# ----------------------------------------
|
||||
|
||||
# shows events of the day for this user
|
||||
def needaction_domain_get(self, cr, uid, domain=[], context={}):
|
||||
return [('date','<=',time.strftime('%Y-%M-%D 23:59:59')), ('date_deadline','>=', time.strftime('%Y-%M-%D 00:00:00')), ('user_id','=',uid)]
|
||||
|
||||
def case_get_note_msg_prefix(self, cr, uid, id, context=None):
|
||||
return 'Meeting'
|
||||
|
||||
def case_open_send_note(self, cr, uid, ids, context=None):
|
||||
return self.message_append_note(cr, uid, ids, body=_("Meeting has been <b>confirmed</b>."), context=context)
|
||||
return self.message_post(cr, uid, ids, body=_("Meeting <b>confirmed</b>."), context=context)
|
||||
|
||||
def case_close_send_note(self, cr, uid, ids, context=None):
|
||||
return self.message_append_note(cr, uid, ids, body=_("Meeting has been <b>done</b>."), context=context)
|
||||
return self.message_post(cr, uid, ids, body=_("Meeting <b>completed</b>."), context=context)
|
||||
|
||||
|
||||
|
|
|
@ -170,7 +170,8 @@
|
|||
<page string="Invitation Detail">
|
||||
<button string="Invite People"
|
||||
name="%(base_calendar.action_view_calendar_invite_attendee_wizard)d"
|
||||
icon="terp-partner" type="action"
|
||||
type="action"
|
||||
attrs="{'readonly': [('state', '=', 'done')]}"
|
||||
context="{'model' : 'crm.meeting', 'attendee_field':'attendee_ids'}" colspan="2"/>
|
||||
<field name="attendee_ids" widget="one2many" mode="tree">
|
||||
<tree string="Invitation details" editable="top">
|
||||
|
@ -182,17 +183,16 @@
|
|||
<button name="do_tentative"
|
||||
states="needs-action,declined,accepted"
|
||||
string="Uncertain" type="object"
|
||||
icon="terp-crm" />
|
||||
/>
|
||||
<button name="do_accept" string="Accept"
|
||||
states="needs-action,tentative,declined"
|
||||
type="object" icon="gtk-apply" />
|
||||
type="object" />
|
||||
<button name="do_decline" string="Decline"
|
||||
states="needs-action,tentative,accepted"
|
||||
type="object" icon="gtk-cancel" />
|
||||
type="object" />
|
||||
<button
|
||||
name="%(base_calendar.action_view_calendar_invite_attendee_wizard)d"
|
||||
string="Delegate" type="action"
|
||||
icon="gtk-sort-descending"
|
||||
states="needs-action,tentative,declined,accepted"
|
||||
context="{'model' : 'calendar.attendee', 'attendee_field' : 'child_ids'}" />
|
||||
</tree>
|
||||
|
@ -200,16 +200,16 @@
|
|||
<header>
|
||||
<button name="do_tentative" type="object"
|
||||
states="needs-action,declined,accepted"
|
||||
string="Uncertain" icon="terp-crm" />
|
||||
string="Uncertain" />
|
||||
<button name="do_accept" type="object"
|
||||
states="needs-action,tentative,declined"
|
||||
string="Accept" icon="gtk-apply" />
|
||||
string="Accept" />
|
||||
<button name="do_decline" type="object"
|
||||
states="needs-action,tentative,accepted"
|
||||
string="Decline" icon="gtk-cancel" />
|
||||
string="Decline" />
|
||||
<button name="%(base_calendar.action_view_calendar_invite_attendee_wizard)d" type="action"
|
||||
states="needs-action,tentative,declined,accepted"
|
||||
string="Delegate" icon="gtk-sort-descending"
|
||||
string="Delegate"
|
||||
context="{'model' : 'calendar.attendee', 'attendee_field' : 'child_ids'}" />
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,open,done"/>
|
||||
</header>
|
||||
|
@ -244,13 +244,13 @@
|
|||
<field name="name">CRM - Meetings Tree</field>
|
||||
<field name="model">crm.meeting</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Meetings" fonts="bold:needaction_pending==True">
|
||||
<tree string="Meetings" fonts="bold:message_unread==True">
|
||||
<field name="name" string="Subject" />
|
||||
<field name="user_id"/>
|
||||
<field name="date"/>
|
||||
<field name="state" invisible="True"/>
|
||||
<field name="duration" />
|
||||
<field name="needaction_pending" invisible="1"/>
|
||||
<field name="message_unread" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
@ -287,9 +287,9 @@
|
|||
<field name="arch" type="xml">
|
||||
<search string="Search Meetings">
|
||||
<field name="name" string="Meeting" filter_domain="[('name','ilike',self)]"/>
|
||||
<filter string="Inbox" help="Unread messages" icon="terp-mail-message-new" name="needaction_pending" domain="[('needaction_pending','=',True)]"/>
|
||||
<filter string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
|
||||
<separator/>
|
||||
<filter string="My Meetings" help="My Meetings" icon="terp-personal" domain="[('user_id','=',uid)]"/>
|
||||
<filter string="My Meetings" help="My Meetings" domain="[('user_id','=',uid)]"/>
|
||||
<field name="user_id"/>
|
||||
<field name="partner_ids"/>
|
||||
</search>
|
||||
|
|
|
@ -297,55 +297,31 @@ class base_stage(object):
|
|||
destination=False)
|
||||
|
||||
def remind_user(self, cr, uid, ids, context=None, attach=False, destination=True):
|
||||
mail_message = self.pool.get('mail.message')
|
||||
for case in self.browse(cr, uid, ids, context=context):
|
||||
if not destination and not case.email_from:
|
||||
return False
|
||||
if not case.user_id.email:
|
||||
return False
|
||||
if destination and case.section_id.user_id:
|
||||
case_email = case.section_id.user_id.email
|
||||
else:
|
||||
case_email = case.user_id.email
|
||||
if 'message_post' in self:
|
||||
for case in self.browse(cr, uid, ids, context=context):
|
||||
if destination:
|
||||
recipient_id = case.user_id.partner_id.id
|
||||
else:
|
||||
if not case.email_from:
|
||||
return False
|
||||
recipient_id = self.pool.get('res.partner').find_or_create(cr, uid, case.email_from, context=context)
|
||||
|
||||
src = case_email
|
||||
dest = case.user_id.email or ""
|
||||
body = case.description or ""
|
||||
for message in case.message_ids:
|
||||
if message.email_from and message.body_text:
|
||||
body = message.body_text
|
||||
break
|
||||
body = case.description or ""
|
||||
for message in case.message_ids:
|
||||
if message.type == 'email' and message.body:
|
||||
body = message.body
|
||||
break
|
||||
body = self.format_body(body)
|
||||
attach_to_send = {}
|
||||
if attach:
|
||||
attach_ids = self.pool.get('ir.attachment').search(cr, uid, [('res_model', '=', self._name), ('res_id', '=', case.id)])
|
||||
attach_to_send = self.pool.get('ir.attachment').read(cr, uid, attach_ids, ['datas_fname', 'datas'])
|
||||
attach_to_send = dict(map(lambda x: (x['datas_fname'], x['datas'].decode('base64')), attach_to_send))
|
||||
|
||||
if not destination:
|
||||
src, dest = dest, case.email_from
|
||||
if body and case.user_id.signature:
|
||||
if body:
|
||||
body += '\n\n%s' % (case.user_id.signature)
|
||||
else:
|
||||
body = '\n\n%s' % (case.user_id.signature)
|
||||
|
||||
body = self.format_body(body)
|
||||
|
||||
attach_to_send = {}
|
||||
|
||||
if attach:
|
||||
attach_ids = self.pool.get('ir.attachment').search(cr, uid, [('res_model', '=', self._name), ('res_id', '=', case.id)])
|
||||
attach_to_send = self.pool.get('ir.attachment').read(cr, uid, attach_ids, ['datas_fname', 'datas'])
|
||||
attach_to_send = dict(map(lambda x: (x['datas_fname'], base64.decodestring(x['datas'])), attach_to_send))
|
||||
|
||||
# Send an email
|
||||
subject = "Reminder: [%s] %s" % (str(case.id), case.name, )
|
||||
mail_message.schedule_with_attach(cr, uid,
|
||||
src,
|
||||
[dest],
|
||||
subject,
|
||||
body,
|
||||
model=self._name,
|
||||
reply_to=case.section_id.reply_to,
|
||||
res_id=case.id,
|
||||
attachments=attach_to_send,
|
||||
context=context
|
||||
)
|
||||
subject = "Reminder: [%s] %s" % (case.id, case.name)
|
||||
self.message_post(cr, uid, case.id, body=body,
|
||||
subject=subject, attachments=attach_to_send,
|
||||
partner_ids=[recipient_id], context=context)
|
||||
return True
|
||||
|
||||
def _check(self, cr, uid, ids=False, context=None):
|
||||
|
@ -360,17 +336,6 @@ class base_stage(object):
|
|||
def format_mail(self, obj, body):
|
||||
return self.pool.get('base.action.rule').format_mail(obj, body)
|
||||
|
||||
def message_thread_followers(self, cr, uid, ids, context=None):
|
||||
res = {}
|
||||
for case in self.browse(cr, uid, ids, context=context):
|
||||
l=[]
|
||||
if case.email_cc:
|
||||
l.append(case.email_cc)
|
||||
if case.user_id and case.user_id.email:
|
||||
l.append(case.user_id.email)
|
||||
res[case.id] = l
|
||||
return res
|
||||
|
||||
# ******************************
|
||||
# Notifications
|
||||
# ******************************
|
||||
|
@ -395,31 +360,31 @@ class base_stage(object):
|
|||
def case_open_send_note(self, cr, uid, ids, context=None):
|
||||
for id in ids:
|
||||
msg = _('%s has been <b>opened</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
|
||||
self.message_append_note(cr, uid, [id], body=msg, context=context)
|
||||
self.message_post(cr, uid, [id], body=msg, context=context)
|
||||
return True
|
||||
|
||||
def case_close_send_note(self, cr, uid, ids, context=None):
|
||||
for id in ids:
|
||||
msg = _('%s has been <b>closed</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
|
||||
self.message_append_note(cr, uid, [id], body=msg, context=context)
|
||||
self.message_post(cr, uid, [id], body=msg, context=context)
|
||||
return True
|
||||
|
||||
def case_cancel_send_note(self, cr, uid, ids, context=None):
|
||||
for id in ids:
|
||||
msg = _('%s has been <b>canceled</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
|
||||
self.message_append_note(cr, uid, [id], body=msg, context=context)
|
||||
self.message_post(cr, uid, [id], body=msg, context=context)
|
||||
return True
|
||||
|
||||
def case_pending_send_note(self, cr, uid, ids, context=None):
|
||||
for id in ids:
|
||||
msg = _('%s is now <b>pending</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
|
||||
self.message_append_note(cr, uid, [id], body=msg, context=context)
|
||||
self.message_post(cr, uid, [id], body=msg, context=context)
|
||||
return True
|
||||
|
||||
def case_reset_send_note(self, cr, uid, ids, context=None):
|
||||
for id in ids:
|
||||
msg = _('%s has been <b>renewed</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
|
||||
self.message_append_note(cr, uid, [id], body=msg, context=context)
|
||||
self.message_post(cr, uid, [id], body=msg, context=context)
|
||||
return True
|
||||
|
||||
def case_escalate_send_note(self, cr, uid, ids, new_section=None, context=None):
|
||||
|
@ -428,5 +393,5 @@ class base_stage(object):
|
|||
msg = '%s has been <b>escalated</b> to <b>%s</b>.' % (self.case_get_note_msg_prefix(cr, uid, id, context=context), new_section.name)
|
||||
else:
|
||||
msg = '%s has been <b>escalated</b>.' % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
|
||||
self.message_append_note(cr, uid, [id], 'System Notification', msg, context=context)
|
||||
self.message_post(cr, uid, [id], body=msg, context=context)
|
||||
return True
|
||||
|
|
|
@ -179,13 +179,13 @@ class base_state(object):
|
|||
# Notifications
|
||||
# ******************************
|
||||
|
||||
def case_get_note_msg_prefix(self, cr, uid, id, context=None):
|
||||
return ''
|
||||
def case_get_note_msg_prefix(self, cr, uid, id, context=None):
|
||||
return ''
|
||||
|
||||
def case_open_send_note(self, cr, uid, ids, context=None):
|
||||
for id in ids:
|
||||
msg = _('%s has been <b>opened</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
|
||||
self.message_append_note(cr, uid, [id], body=msg, context=context)
|
||||
self.message_post(cr, uid, [id], body=msg, context=context)
|
||||
return True
|
||||
|
||||
def case_escalate_send_note(self, cr, uid, ids, new_section=None, context=None):
|
||||
|
@ -194,29 +194,29 @@ class base_state(object):
|
|||
msg = '%s has been <b>escalated</b> to <b>%s</b>.' % (self.case_get_note_msg_prefix(cr, uid, id, context=context), new_section.name)
|
||||
else:
|
||||
msg = '%s has been <b>escalated</b>.' % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
|
||||
self.message_append_note(cr, uid, [id], 'System Notification', msg, context=context)
|
||||
self.message_post(cr, uid, [id], body=msg, context=context)
|
||||
return True
|
||||
|
||||
def case_close_send_note(self, cr, uid, ids, context=None):
|
||||
for id in ids:
|
||||
msg = _('%s has been <b>closed</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
|
||||
self.message_append_note(cr, uid, [id], body=msg, context=context)
|
||||
self.message_post(cr, uid, [id], body=msg, context=context)
|
||||
return True
|
||||
|
||||
def case_cancel_send_note(self, cr, uid, ids, context=None):
|
||||
for id in ids:
|
||||
msg = _('%s has been <b>canceled</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
|
||||
self.message_append_note(cr, uid, [id], body=msg, context=context)
|
||||
self.message_post(cr, uid, [id], body=msg, context=context)
|
||||
return True
|
||||
|
||||
def case_pending_send_note(self, cr, uid, ids, context=None):
|
||||
for id in ids:
|
||||
msg = _('%s is now <b>pending</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
|
||||
self.message_append_note(cr, uid, [id], body=msg, context=context)
|
||||
self.message_post(cr, uid, [id], body=msg, context=context)
|
||||
return True
|
||||
|
||||
def case_reset_send_note(self, cr, uid, ids, context=None):
|
||||
for id in ids:
|
||||
msg = _('%s has been <b>renewed</b>.') % (self.case_get_note_msg_prefix(cr, uid, id, context=context))
|
||||
self.message_append_note(cr, uid, [id], body=msg, context=context)
|
||||
self.message_post(cr, uid, [id], body=msg, context=context)
|
||||
return True
|
||||
|
|
|
@ -46,21 +46,11 @@ class base_action_rule(osv.osv):
|
|||
}
|
||||
|
||||
def email_send(self, cr, uid, obj, emails, body, emailfrom=tools.config.get('email_from', False), context=None):
|
||||
mail_message = self.pool.get('mail.message')
|
||||
body = self.format_mail(obj, body)
|
||||
if not emailfrom:
|
||||
if hasattr(obj, 'user_id') and obj.user_id and obj.user_id.email:
|
||||
emailfrom = obj.user_id.email
|
||||
|
||||
name = '[%d] %s' % (obj.id, tools.ustr(obj.name))
|
||||
emailfrom = tools.ustr(emailfrom)
|
||||
if hasattr(obj, 'section_id') and obj.section_id and obj.section_id.alias_id:
|
||||
mail_id = super(base_action_rule, self).email_send(cr, uid, obj, emails, body, emailfrom=emailfrom, context=context)
|
||||
if mail_id and hasattr(obj, 'section_id') and obj.section_id and obj.section_id.alias_id:
|
||||
reply_to = obj.section_id.alias_id.name_get()[0][1]
|
||||
else:
|
||||
reply_to = emailfrom
|
||||
if not emailfrom:
|
||||
raise osv.except_osv(_('Error!'), _("There is no email for your company address."))
|
||||
return mail_message.schedule_with_attach(cr, uid, emailfrom, emails, name, body, model=obj._name, reply_to=reply_to, res_id=obj.id)
|
||||
self.pool.get('mail.mail').write(cr, uid, [mail_id], {'reply_to': reply_to}, context=context)
|
||||
return mail_id
|
||||
|
||||
def do_check(self, cr, uid, action, obj, context=None):
|
||||
ok = super(base_action_rule, self).do_check(cr, uid, action, obj, context=context)
|
||||
|
@ -105,8 +95,8 @@ class base_action_rule(osv.osv):
|
|||
write['email_cc'] = obj.act_email_cc
|
||||
|
||||
# Put state change by rule in communication history
|
||||
if hasattr(obj, 'state') and hasattr(obj, 'message_append') and action.act_state:
|
||||
model_obj.message_append(cr, uid, [obj], _(action.act_state))
|
||||
if hasattr(obj, 'state') and hasattr(obj, 'message_post') and action.act_state:
|
||||
model_obj.message_post(cr, uid, [obj], _(action.act_state), context=context)
|
||||
|
||||
model_obj.write(cr, uid, [obj.id], write, context)
|
||||
super(base_action_rule, self).do_action(cr, uid, action, model_obj, obj, context=context)
|
||||
|
|
|
@ -55,14 +55,14 @@
|
|||
</record>
|
||||
|
||||
<!-- notify all employees of module installation -->
|
||||
<function model="mail.group" name="message_append_note">
|
||||
<!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
|
||||
<value eval="[ref('mail.group_all_employees')]"/>
|
||||
<value>Module CRM has been installed</value>
|
||||
<value>From the top menu Sales, you can: trace leads and opportunities, get accurate forecast on your sales pipeline, plan meetings and phonecalls, get realtime statistics and efficiently organize the communication with your prospects.
|
||||
|
||||
To manage quotations and sale orders, install the module "Sales Management".</value>
|
||||
</function>
|
||||
<record model="mail.message" id="module_install_notification">
|
||||
<field name="model">mail.group</field>
|
||||
<field name="res_id" ref="mail.group_all_employees"/>
|
||||
<field name="type">notification</field>
|
||||
<field name="subject">CRM application installed!</field>
|
||||
<field name="body">From the top Sales menu you can track leads and opportunities, get accurate forecast on your sales pipeline, plan meetings and phonecalls, get realtime statistics and efficiently organize the communication with your prospects.
|
||||
To manage quotations and sale orders, install the "Sales Management" application.</field>
|
||||
</record>
|
||||
|
||||
<record model="mail.alias" id="default_sales_alias">
|
||||
<field name="alias_name">sales</field>
|
||||
|
@ -70,6 +70,5 @@ To manage quotations and sale orders, install the module "Sales Management".</va
|
|||
<field name="alias_user_id" ref="base.user_root"/>
|
||||
<field name="alias_defaults">{'type':'lead'}</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -19,11 +19,9 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import binascii
|
||||
from base_status.base_stage import base_stage
|
||||
import crm
|
||||
from datetime import datetime
|
||||
from mail.mail_message import to_email
|
||||
from osv import fields, osv
|
||||
import time
|
||||
import tools
|
||||
|
@ -40,8 +38,7 @@ class crm_lead(base_stage, osv.osv):
|
|||
_name = "crm.lead"
|
||||
_description = "Lead/Opportunity"
|
||||
_order = "priority,date_action,id desc"
|
||||
_inherit = ['ir.needaction_mixin', 'mail.thread']
|
||||
_mail_compose_message = True
|
||||
_inherit = ['mail.thread','ir.needaction_mixin']
|
||||
|
||||
def _get_default_section_id(self, cr, uid, context=None):
|
||||
""" Gives default section by checking if present in the context """
|
||||
|
@ -175,16 +172,6 @@ class crm_lead(base_stage, osv.osv):
|
|||
else:
|
||||
return [('id', '=', '0')]
|
||||
|
||||
def _get_email_subject(self, cr, uid, ids, fields, args, context=None):
|
||||
res = {}
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
res[obj.id] = ''
|
||||
for msg in obj.message_ids:
|
||||
if msg.email_from:
|
||||
res[obj.id] = msg.subject
|
||||
break
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'partner_id': fields.many2one('res.partner', 'Partner', ondelete='set null',
|
||||
select=True, help="Optional linked partner, usually after conversion of the lead"),
|
||||
|
@ -228,7 +215,6 @@ class crm_lead(base_stage, osv.osv):
|
|||
When the case is over, the state is set to \'Done\'.\
|
||||
If the case needs to be reviewed then the state is \
|
||||
set to \'Pending\'.'),
|
||||
'subjects': fields.function(_get_email_subject, fnct_search=_history_search, string='Subject of Email', type='char', size=64),
|
||||
|
||||
# Only used for type opportunity
|
||||
'probability': fields.float('Success Rate (%)',group_operator="avg"),
|
||||
|
@ -449,7 +435,7 @@ class crm_lead(base_stage, osv.osv):
|
|||
oldest_id = opportunity_ids[0]
|
||||
return self.browse(cr, uid, oldest_id, context=context)
|
||||
|
||||
def _mail_body_text(self, cr, uid, lead, fields, title=False, context=None):
|
||||
def _mail_body(self, cr, uid, lead, fields, title=False, context=None):
|
||||
body = []
|
||||
if title:
|
||||
body.append("%s\n" % (title))
|
||||
|
@ -486,11 +472,11 @@ class crm_lead(base_stage, osv.osv):
|
|||
for opportunity in opportunities:
|
||||
subject.append(opportunity.name)
|
||||
title = "%s : %s" % (merge_message, opportunity.name)
|
||||
details.append(self._mail_body_text(cr, uid, opportunity, fields, title=title, context=context))
|
||||
details.append(self._mail_body(cr, uid, opportunity, fields, title=title, context=context))
|
||||
|
||||
subject = subject[0] + ", ".join(subject[1:])
|
||||
details = "\n\n".join(details)
|
||||
return self.message_append_note(cr, uid, [opportunity_id], subject=subject, body=details)
|
||||
return self.message_post(cr, uid, [opportunity_id], body=details, subject=subject, context=context)
|
||||
|
||||
def _merge_opportunity_history(self, cr, uid, opportunity_id, opportunities, context=None):
|
||||
message = self.pool.get('mail.message')
|
||||
|
@ -546,7 +532,7 @@ class crm_lead(base_stage, osv.osv):
|
|||
oldest = self._merge_find_oldest(cr, uid, ids, context=context)
|
||||
if ctx_opportunities :
|
||||
first_opportunity = ctx_opportunities[0]
|
||||
tail_opportunities = opportunities_list
|
||||
tail_opportunities = opportunities_list + ctx_opportunities[1:]
|
||||
else:
|
||||
first_opportunity = opportunities_list[0]
|
||||
tail_opportunities = opportunities_list[1:]
|
||||
|
@ -606,19 +592,13 @@ class crm_lead(base_stage, osv.osv):
|
|||
for lead in self.browse(cr, uid, ids, context=context):
|
||||
if lead.state in ('done', 'cancel'):
|
||||
continue
|
||||
if user_ids or section_id:
|
||||
self.allocate_salesman(cr, uid, [lead.id], user_ids, section_id, context=context)
|
||||
|
||||
vals = self._convert_opportunity_data(cr, uid, lead, customer, section_id, context=context)
|
||||
self.write(cr, uid, [lead.id], vals, context=context)
|
||||
|
||||
self.convert_opportunity_send_note(cr, uid, lead, context=context)
|
||||
#TOCHECK: why need to change partner details in all messages of lead ?
|
||||
if lead.partner_id:
|
||||
msg_ids = [ x.id for x in lead.message_ids]
|
||||
mail_message.write(cr, uid, msg_ids, {
|
||||
'partner_id': lead.partner_id.id
|
||||
}, context=context)
|
||||
|
||||
if user_ids or section_id:
|
||||
self.allocate_salesman(cr, uid, ids, user_ids, section_id, context=context)
|
||||
|
||||
return True
|
||||
|
||||
def _lead_create_contact(self, cr, uid, lead, name, is_company, parent_id=False, context=None):
|
||||
|
@ -630,7 +610,7 @@ class crm_lead(base_stage, osv.osv):
|
|||
'parent_id': parent_id,
|
||||
'phone': lead.phone,
|
||||
'mobile': lead.mobile,
|
||||
'email': lead.email_from and to_email(lead.email_from)[0],
|
||||
'email': lead.email_from and tools.email_split(lead.email_from)[0],
|
||||
'fax': lead.fax,
|
||||
'title': lead.title and lead.title.id or False,
|
||||
'function': lead.function,
|
||||
|
@ -650,7 +630,7 @@ class crm_lead(base_stage, osv.osv):
|
|||
partner_id = False
|
||||
if lead.partner_name and lead.contact_name:
|
||||
partner_id = self._lead_create_contact(cr, uid, lead, lead.partner_name, True, context=context)
|
||||
self._lead_create_contact(cr, uid, lead, lead.contact_name, False, partner_id, context=context)
|
||||
partner_id = self._lead_create_contact(cr, uid, lead, lead.contact_name, False, partner_id, context=context)
|
||||
elif lead.partner_name and not lead.contact_name:
|
||||
partner_id = self._lead_create_contact(cr, uid, lead, lead.partner_name, True, context=context)
|
||||
elif not lead.partner_name and lead.contact_name:
|
||||
|
@ -678,32 +658,16 @@ class crm_lead(base_stage, osv.osv):
|
|||
if context is None:
|
||||
context = {}
|
||||
partner_ids = {}
|
||||
force_partner_id = partner_id
|
||||
for lead in self.browse(cr, uid, ids, context=context):
|
||||
if action == 'create':
|
||||
if not partner_id:
|
||||
partner_id = self._create_lead_partner(cr, uid, lead, context)
|
||||
partner_id = force_partner_id or self._create_lead_partner(cr, uid, lead, context=context)
|
||||
self._lead_set_partner(cr, uid, lead, partner_id, context=context)
|
||||
partner_ids[lead.id] = partner_id
|
||||
return partner_ids
|
||||
|
||||
def _send_mail_to_salesman(self, cr, uid, lead, context=None):
|
||||
"""
|
||||
Send mail to salesman with updated Lead details.
|
||||
@ lead: browse record of 'crm.lead' object.
|
||||
"""
|
||||
#TOFIX: mail template should be used here instead of fix subject, body text.
|
||||
message = self.pool.get('mail.message')
|
||||
email_to = lead.user_id and lead.user_id.email
|
||||
if not email_to:
|
||||
return False
|
||||
|
||||
email_from = lead.section_id and lead.section_id.user_id and lead.section_id.user_id.email or email_to
|
||||
partner = lead.partner_id and lead.partner_id.name or lead.partner_name
|
||||
subject = "lead %s converted into opportunity" % lead.name
|
||||
body = "Info \n Id : %s \n Subject: %s \n Partner: %s \n Description : %s " % (lead.id, lead.name, lead.partner_id.name, lead.description)
|
||||
return message.schedule_with_attach(cr, uid, email_from, [email_to], subject, body)
|
||||
|
||||
|
||||
def allocate_salesman(self, cr, uid, ids, user_ids, team_id=False, context=None):
|
||||
index = 0
|
||||
for lead_id in ids:
|
||||
|
@ -821,14 +785,13 @@ class crm_lead(base_stage, osv.osv):
|
|||
if custom_values is None: custom_values = {}
|
||||
custom_values.update({
|
||||
'name': msg.get('subject') or _("No Subject"),
|
||||
'description': msg.get('body_text'),
|
||||
'description': msg.get('body'),
|
||||
'email_from': msg.get('from'),
|
||||
'email_cc': msg.get('cc'),
|
||||
'user_id': False,
|
||||
})
|
||||
if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
|
||||
custom_values['priority'] = msg.get('priority')
|
||||
custom_values.update(self.message_partner_by_email(cr, uid, msg.get('from', False), context=context))
|
||||
return super(crm_lead, self).message_new(cr, uid, msg, custom_values=custom_values, context=context)
|
||||
|
||||
def message_update(self, cr, uid, ids, msg, update_vals=None, context=None):
|
||||
|
@ -841,18 +804,18 @@ class crm_lead(base_stage, osv.osv):
|
|||
if update_vals is None: update_vals = {}
|
||||
|
||||
if msg.get('priority') in dict(crm.AVAILABLE_PRIORITIES):
|
||||
vals['priority'] = msg.get('priority')
|
||||
update_vals['priority'] = msg.get('priority')
|
||||
maps = {
|
||||
'cost':'planned_cost',
|
||||
'revenue': 'planned_revenue',
|
||||
'probability':'probability',
|
||||
}
|
||||
for line in msg.get('body_text', '').split('\n'):
|
||||
for line in msg.get('body', '').split('\n'):
|
||||
line = line.strip()
|
||||
res = tools.misc.command_re.match(line)
|
||||
if res and maps.get(res.group(1).lower()):
|
||||
key = maps.get(res.group(1).lower())
|
||||
vals[key] = res.group(2).lower()
|
||||
update_vals[key] = res.group(2).lower()
|
||||
|
||||
return super(crm_lead, self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context)
|
||||
|
||||
|
@ -860,15 +823,10 @@ class crm_lead(base_stage, osv.osv):
|
|||
# OpenChatter methods and notifications
|
||||
# ----------------------------------------
|
||||
|
||||
def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
|
||||
""" Add 'user_id' to the monitored fields """
|
||||
res = super(crm_lead, self).message_get_monitored_follower_fields(cr, uid, ids, context=context)
|
||||
return res + ['user_id']
|
||||
|
||||
def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
|
||||
""" Override of the (void) default notification method. """
|
||||
stage_name = self.pool.get('crm.case.stage').name_get(cr, uid, [stage_id], context=context)[0][1]
|
||||
return self.message_append_note(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context)
|
||||
return self.message_post(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context)
|
||||
|
||||
def case_get_note_msg_prefix(self, cr, uid, lead, context=None):
|
||||
if isinstance(lead, (int, long)):
|
||||
|
@ -878,33 +836,33 @@ class crm_lead(base_stage, osv.osv):
|
|||
def create_send_note(self, cr, uid, ids, context=None):
|
||||
for id in ids:
|
||||
message = _("%s has been <b>created</b>.")% (self.case_get_note_msg_prefix(cr, uid, id, context=context))
|
||||
self.message_append_note(cr, uid, [id], body=message, context=context)
|
||||
self.message_post(cr, uid, [id], body=message, context=context)
|
||||
return True
|
||||
|
||||
def case_mark_lost_send_note(self, cr, uid, ids, context=None):
|
||||
message = _("Opportunity has been <b>lost</b>.")
|
||||
return self.message_append_note(cr, uid, ids, body=message, context=context)
|
||||
return self.message_post(cr, uid, ids, body=message, context=context)
|
||||
|
||||
def case_mark_won_send_note(self, cr, uid, ids, context=None):
|
||||
message = _("Opportunity has been <b>won</b>.")
|
||||
return self.message_append_note(cr, uid, ids, body=message, context=context)
|
||||
return self.message_post(cr, uid, ids, body=message, context=context)
|
||||
|
||||
def schedule_phonecall_send_note(self, cr, uid, ids, phonecall_id, action, context=None):
|
||||
phonecall = self.pool.get('crm.phonecall').browse(cr, uid, [phonecall_id], context=context)[0]
|
||||
if action == 'log': prefix = 'Logged'
|
||||
else: prefix = 'Scheduled'
|
||||
message = _("<b>%s a call</b> for the <em>%s</em>.") % (prefix, phonecall.date)
|
||||
return self.message_append_note(cr, uid, ids, body=message, context=context)
|
||||
return self.message_post(cr, uid, ids, body=message, context=context)
|
||||
|
||||
def _lead_set_partner_send_note(self, cr, uid, ids, context=None):
|
||||
for lead in self.browse(cr, uid, ids, context=context):
|
||||
message = _("%s <b>partner</b> is now set to <em>%s</em>." % (self.case_get_note_msg_prefix(cr, uid, lead, context=context), lead.partner_id.name))
|
||||
lead.message_append_note(body=message)
|
||||
lead.message_post(body=message)
|
||||
return True
|
||||
|
||||
def convert_opportunity_send_note(self, cr, uid, lead, context=None):
|
||||
message = _("Lead has been <b>converted to an opportunity</b>.")
|
||||
lead.message_append_note(body=message)
|
||||
lead.message_post(body=message)
|
||||
return True
|
||||
|
||||
crm_lead()
|
||||
|
|
|
@ -552,86 +552,67 @@ Andrew</field>
|
|||
<field name="subject">Plan to buy a Laptop</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="res_id" ref="crm_case_15"/>
|
||||
<field name="content_subtype">html</field>
|
||||
<field name="body_html"><![CDATA[Email0 inquiry]]><div><font size="2">Hello,</font></div><div><font size="2"><br></font></div><div><font size="2">I am interested in your company's product and I plan to buy a new laptop having latest technologies and affordable price.</font></div><div><font size="2">Can you please send me product catalogue?</font></div></field>
|
||||
<field name="body"><![CDATA[Email0 inquiry]]><div><font size="2">Hello,</font></div><div><font size="2"><br></font></div><div><font size="2">I am interested in your company's product and I plan to buy a new laptop having latest technologies and affordable price.</font></div><div><font size="2">Can you please send me product catalogue?</font></div></field>
|
||||
<field name="type">email</field>
|
||||
<field name="state">received</field>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
</record>
|
||||
<record id="message_note0" model="mail.message">
|
||||
<field name="subject">Re: Plan to buy a Laptop</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="res_id" ref="crm_case_15"/>
|
||||
<field name="parent_id" ref="message_email0"/>
|
||||
<field name="content_subtype">plain</field>
|
||||
<field name="body_text">Dear Customer,
|
||||
<field name="type">comment</field>
|
||||
<field name="body">Dear Customer,
|
||||
Thanks for showing interest in our products.
|
||||
We have attached the catalogue,
|
||||
We would like to know your interests, Let us know if we can call you for more details.
|
||||
|
||||
Thanks</field>
|
||||
<field name="type">email</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="state">sent</field>
|
||||
<field name="parent_id" ref="message_email0"/>
|
||||
<field name="author_id" ref="base.partner_root"/>
|
||||
</record>
|
||||
<record id="message_note0_comment0" model="mail.message">
|
||||
<field name="subject">Re: Plan to buy a Laptop</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="res_id" ref="crm_case_15"/>
|
||||
<field name="content_subtype">html</field>
|
||||
<field name="body_html"><div>Thanks for the information,</div><div>I will visit the store soon.</div></field>
|
||||
<field name="type">comment</field>
|
||||
<field name="body"><div>Thanks for the information,</div><div>I will visit the store soon.</div></field>
|
||||
<field name="parent_id" ref="message_note0"/>
|
||||
<field name="type">email</field>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="state">received</field>
|
||||
<field name="author_id" ref="base.partner_demo"/>
|
||||
</record>
|
||||
<record id="message_note0_comment1" model="mail.message">
|
||||
<field name="subject">Re: Plan to buy a Laptop</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="res_id" ref="crm_case_15"/>
|
||||
<field name="content_subtype">html</field>
|
||||
<field name="body_html"><font color="#1f1f1f">Can you tell me if the store is open at 9:00 PM?</b></font></field>
|
||||
<field name="type">comment</field>
|
||||
<field name="body"><font color="#1f1f1f">Can you tell me if the store is open at 9:00 PM?</b></font></field>
|
||||
<field name="parent_id" ref="message_note0"/>
|
||||
<field name="type">email</field>
|
||||
<field name="state">received</field>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
<field name="author_id" ref="base.partner_demo"/>
|
||||
</record>
|
||||
<record id="message_email1" model="mail.message">
|
||||
<field name="subject">Re: Plan to buy a Laptop</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="res_id" ref="crm_case_15"/>
|
||||
<field name="content_subtype">plain</field>
|
||||
<field name="body_text">Yes, its open till 10:00 PM, you are welcome!</field>
|
||||
<field name="body">Yes, its open till 10:00 PM, you are welcome!</field>
|
||||
<field name="type">email</field>
|
||||
<field name="state">sent</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="author_id" ref="base.partner_root"/>
|
||||
</record>
|
||||
|
||||
<record id="message_email_12" model="mail.message">
|
||||
<field name="subject">Inquiry</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="res_id" ref="crm_case_1"/>
|
||||
<field name="content_subtype">plain</field>
|
||||
<field name="body_text">Hello,
|
||||
<field name="body">Hello,
|
||||
I am Jason from Le Club SARL,
|
||||
I am intertested to attend Training organized in your company,
|
||||
Can you send details,</field>
|
||||
<field name="type">email</field>
|
||||
<field name="state">received</field>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
</record>
|
||||
|
||||
<record id="message_email_13" model="mail.message">
|
||||
<field name="subject">Need Details</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="res_id" ref="crm_case_2"/>
|
||||
<field name="content_subtype">plain</field>
|
||||
<field name="body_text">Want to know features and benifits to use the new software.</field>
|
||||
<field name="body">Want to know features and benifits to use the new software.</field>
|
||||
<field name="type">comment</field>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- Call Function to set the opportunities as Unread -->
|
||||
<function model="crm.lead" name="message_mark_as_unread"
|
||||
eval="[ ref('crm_case_15'), ref('crm_case_16'),
|
||||
|
|
|
@ -246,7 +246,7 @@
|
|||
<field name="name">Leads</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Leads" fonts="bold:needaction_pending==True" colors="grey:state in ('cancel', 'done')">
|
||||
<tree string="Leads" fonts="bold:message_unread==True" colors="grey:state in ('cancel', 'done')">
|
||||
<field name="date_deadline" invisible="1"/>
|
||||
<field name="create_date" groups="base.group_no_one"/>
|
||||
<field name="name"/>
|
||||
|
@ -261,8 +261,7 @@
|
|||
<field name="type_id" invisible="1"/>
|
||||
<field name="referred" invisible="1"/>
|
||||
<field name="channel_id" invisible="1"/>
|
||||
<field name="subjects" invisible="1"/>
|
||||
<field name="needaction_pending" invisible="1"/>
|
||||
<field name="message_unread" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
@ -296,7 +295,7 @@
|
|||
<field name="user_id"/>
|
||||
<field name="partner_address_email"/>
|
||||
<field name="message_summary"/>
|
||||
<field name="needaction_pending"/>
|
||||
<field name="message_unread"/>
|
||||
<templates>
|
||||
<t t-name="lead_details">
|
||||
<ul class="oe_kanban_tooltip oe_semantic_html_override">
|
||||
|
@ -342,7 +341,6 @@
|
|||
<img t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)" t-att-title="record.user_id.value" width="24" height="24" class="oe_kanban_avatar"/>
|
||||
</div>
|
||||
<div class="oe_kanban_footer_left">
|
||||
<t t-if="record.needaction_pending.raw_value"><span class="oe_kanban_mail_new">New</span></t>
|
||||
<t t-raw="record.message_summary.raw_value"/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -362,10 +360,8 @@
|
|||
<search string="Search Leads">
|
||||
<field name="name" string="Lead / Customer" filter_domain="['|','|',('partner_name','ilike',self),('email_from','ilike',self),('name','ilike',self)]"/>
|
||||
<field name="categ_ids" string="Category" filter_domain="[('categ_ids','ilike',self)]" />
|
||||
<!-- subjects is not set as store=True so, it is placed outside filter_domain-->
|
||||
<field name="subjects"/>
|
||||
<field name="create_date"/>
|
||||
<filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="needaction_pending" domain="[('needaction_pending','=',True)]"/>
|
||||
<filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
|
||||
<separator/>
|
||||
<filter icon="terp-check" string="New" name="new" help="New Leads" domain="[('state','=','draft')]"/>
|
||||
<filter icon="terp-camera_test" string="Open" name="open" domain="[('state','=','open')]"/>
|
||||
|
@ -545,7 +541,7 @@
|
|||
<field name="name">Opportunities Tree</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Opportunities" fonts="bold:needaction_pending==True" colors="gray:state in ('cancel', 'done');red:date_deadline and (date_deadline < current_date)">
|
||||
<tree string="Opportunities" fonts="bold:message_unread==True" colors="gray:state in ('cancel', 'done');red:date_deadline and (date_deadline < current_date)">
|
||||
<field name="date_deadline" invisible="1"/>
|
||||
<field name="create_date" groups="base.group_no_one"/>
|
||||
<field name="name" string="Opportunity"/>
|
||||
|
@ -555,7 +551,6 @@
|
|||
<field name="title_action" />
|
||||
<field name="channel_id" invisible="1"/>
|
||||
<field name="type_id" invisible="1"/>
|
||||
<field name="subjects" invisible="1"/>
|
||||
<field name="stage_id"/>
|
||||
<field name="planned_revenue" sum="Expected Revenues"/>
|
||||
<field name="probability" widget="progressbar" avg="Avg. of Probability"/>
|
||||
|
@ -563,7 +558,7 @@
|
|||
<field name="user_id"/>
|
||||
<field name="priority" invisible="1"/>
|
||||
<field name="state" groups="base.group_no_one"/>
|
||||
<field name="needaction_pending" invisible="1"/>
|
||||
<field name="message_unread" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
@ -578,7 +573,7 @@
|
|||
<field name="name" string="Opportunity / Customer"
|
||||
filter_domain="['|','|','|',('partner_id','ilike',self),('partner_name','ilike',self),('email_from','ilike',self),('name', 'ilike', self)]"/>
|
||||
<field name="categ_ids" string="Category" filter_domain="[('categ_ids','ilike', self)]" />
|
||||
<filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="needaction_pending" domain="[('needaction_pending','=',True)]"/>
|
||||
<filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
|
||||
<separator/>
|
||||
<filter icon="terp-check" string="New" help="New Opportunities" name="new" domain="[('state','=','draft')]"/>
|
||||
<filter icon="terp-camera_test" string="Open" help="Open Opportunities" name="open" domain="[('state','=','open')]"/>
|
||||
|
|
|
@ -44,7 +44,7 @@ class crm_meeting(osv.Model):
|
|||
def create_send_note(self, cr, uid, ids, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
# update context: if come from phonecall, default state values can make the message_append_note crash
|
||||
# update context: if come from phonecall, default state values can make the message_post crash
|
||||
context.pop('default_state', False)
|
||||
for meeting in self.browse(cr, uid, ids, context=context):
|
||||
# in the message, transpose meeting.date to the timezone of the current user
|
||||
|
@ -53,14 +53,14 @@ class crm_meeting(osv.Model):
|
|||
if meeting.opportunity_id: # meeting can be create from phonecalls or opportunities, therefore checking for the parent
|
||||
lead = meeting.opportunity_id
|
||||
message = _("Meeting linked to the opportunity <em>%s</em> has been <b>created</b> and <b>scheduled</b> on <em>%s</em>.") % (lead.name, meeting_date_tz)
|
||||
lead.message_append_note(_('System Notification'), message)
|
||||
lead.message_post(body=message)
|
||||
elif meeting.phonecall_id:
|
||||
phonecall = meeting.phonecall_id
|
||||
message = _("Meeting linked to the phonecall <em>%s</em> has been <b>created</b> and <b>scheduled</b> on <em>%s</em>.") % (phonecall.name, meeting_date_tz)
|
||||
phonecall.message_append_note(body=message)
|
||||
phonecall.message_post(body=message)
|
||||
else:
|
||||
message = _("A meeting has been <b>scheduled</b> on <em>%s</em>.") % (meeting_date_tz)
|
||||
meeting.message_append_note(body=message)
|
||||
meeting.message_post(body=message)
|
||||
return True
|
||||
|
||||
class calendar_attendee(osv.osv):
|
||||
|
|
|
@ -32,7 +32,7 @@ class crm_phonecall(base_state, osv.osv):
|
|||
_name = "crm.phonecall"
|
||||
_description = "Phonecall"
|
||||
_order = "id desc"
|
||||
_inherit = ['ir.needaction_mixin', 'mail.thread']
|
||||
_inherit = ['mail.thread']
|
||||
_columns = {
|
||||
# base_state required fields
|
||||
'date_action_last': fields.datetime('Last Action', readonly=1),
|
||||
|
@ -177,11 +177,11 @@ class crm_phonecall(base_state, osv.osv):
|
|||
if context is None:
|
||||
context = {}
|
||||
partner_ids = {}
|
||||
force_partner_id = partner_id
|
||||
for call in self.browse(cr, uid, ids, context=context):
|
||||
if action == 'create':
|
||||
if not partner_id:
|
||||
partner_id = self._call_create_partner(cr, uid, call, context=context)
|
||||
self._call_create_partner_address(cr, uid, call, partner_id, context=context)
|
||||
partner_id = force_partner_id or self._call_create_partner(cr, uid, call, context=context)
|
||||
self._call_create_partner_address(cr, uid, call, partner_id, context=context)
|
||||
self._call_set_partner(cr, uid, [call.id], partner_id, context=context)
|
||||
partner_ids[call.id] = partner_id
|
||||
return partner_ids
|
||||
|
@ -266,7 +266,7 @@ class crm_phonecall(base_state, osv.osv):
|
|||
|
||||
def case_reset_send_note(self, cr, uid, ids, context=None):
|
||||
message = _('Phonecall has been <b>reset and set as open</b>.')
|
||||
return self.message_append_note(cr, uid, ids, body=message, context=context)
|
||||
return self.message_post(cr, uid, ids, body=message, context=context)
|
||||
|
||||
def case_open_send_note(self, cr, uid, ids, context=None):
|
||||
lead_obj = self.pool.get('crm.lead')
|
||||
|
@ -280,11 +280,11 @@ class crm_phonecall(base_state, osv.osv):
|
|||
message = _("Phonecall linked to the opportunity <em>%s</em> has been <b>created</b> and <b>scheduled</b> on <em>%s</em>.") % (lead.name, phonecall_date_str)
|
||||
else:
|
||||
message = _("Phonecall has been <b>created and opened</b>.")
|
||||
phonecall.message_append_note(body=message)
|
||||
phonecall.message_post(body=message)
|
||||
return True
|
||||
|
||||
def _call_set_partner_send_note(self, cr, uid, ids, context=None):
|
||||
return self.message_append_note(cr, uid, ids, body=_("Partner has been <b>created</b>."), context=context)
|
||||
return self.message_post(cr, uid, ids, body=_("Partner has been <b>created</b>."), context=context)
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -66,8 +66,7 @@
|
|||
<field name="name">CRM - Phone Calls Tree</field>
|
||||
<field name="model">crm.phonecall</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree fonts="bold:needaction_pending==True" colors="gray:state in ('cancel','done');blue:state in ('pending',)" string="Phone Calls">
|
||||
<field name="needaction_pending" invisible="1"/>
|
||||
<tree colors="gray:state in ('cancel','done');blue:state in ('pending',)" string="Phone Calls">
|
||||
<field name="date"/>
|
||||
<field name="name"/>
|
||||
<field name="partner_id"/>
|
||||
|
@ -76,7 +75,6 @@
|
|||
<field name="categ_id" invisible="1"/>
|
||||
<field name="create_date" invisible="1"/>
|
||||
<field name="opportunity_id" invisible="1"/>
|
||||
<field name="needaction_pending" invisible="1"/>
|
||||
<button string="Convert to Opportunity"
|
||||
name="%(phonecall2opportunity_act)d"
|
||||
states="open,pending"
|
||||
|
@ -166,7 +164,7 @@
|
|||
<field name="name">CRM - Logged Phone Calls Tree</field>
|
||||
<field name="model">crm.phonecall</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Phone Calls" fonts="bold:needaction_pending==True" editable="top">
|
||||
<tree string="Phone Calls" editable="top">
|
||||
<field name="date"/>
|
||||
<field name="name"/>
|
||||
<field name="partner_id"
|
||||
|
@ -180,7 +178,6 @@
|
|||
<field name="state" invisible="1"/>
|
||||
<field name="create_date" invisible="1"/>
|
||||
<field name="opportunity_id" invisible="1"/>
|
||||
<field name="needaction_pending" invisible="1"/>
|
||||
<button string="Schedule Other Call"
|
||||
icon="terp-call-start"
|
||||
name="%(phonecall_to_phonecall_act)d"
|
||||
|
@ -219,7 +216,6 @@
|
|||
<search string="Search Phonecalls">
|
||||
<field name="name" string="Phonecalls"/>
|
||||
<field name="date"/>
|
||||
<filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="needaction_pending" domain="[('needaction_pending','=',True)]"/>
|
||||
<separator/>
|
||||
<filter icon="terp-gtk-go-back-rtl" string="To Do" name="current" domain="[('state','=','open')]"/>
|
||||
<separator/>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
!python {model: mail.compose.message}: |
|
||||
lead_ids = self.pool.get('crm.lead').search(cr, uid, [('email_from','=', 'Mr. John Right <info@customer.com>')])
|
||||
context.update({'active_model': 'crm.lead','active_id': lead_ids[0]})
|
||||
id = self.create(cr, uid, {'body_text': "Merci à l'intérêt pour notre produit.nous vous contacterons bientôt. Merci", 'email_from': 'sales@mycompany.com'}, context=context)
|
||||
id = self.create(cr, uid, {'body': "Merci à l'intérêt pour notre produit.nous vous contacterons bientôt. Merci", 'email_from': 'sales@mycompany.com'}, context=context)
|
||||
try:
|
||||
self.send_mail(cr, uid, [id], context=context)
|
||||
except:
|
||||
|
@ -34,12 +34,6 @@
|
|||
!python {model: crm.lead}: |
|
||||
lead_ids = self.search(cr, uid, [('email_from','=', 'Mr. John Right <info@customer.com>')])
|
||||
self.convert_partner(cr, uid, lead_ids, context=context)
|
||||
-
|
||||
Now, I search customer in regular customer list.
|
||||
-
|
||||
!python {model: crm.lead}: |
|
||||
partner_ids = self.message_partner_by_email(cr, uid, 'Mr. John Right <info@customer.com>')
|
||||
assert partner_ids.get('partner_id'), "Customer is not found in regular customer list."
|
||||
-
|
||||
I convert one phonecall request as a customer and put into regular customer list.
|
||||
-
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
After communicated with customer, I put some notes with contract details.
|
||||
-
|
||||
!python {model: crm.lead}: |
|
||||
self.message_append_note(cr, uid, [ref('crm_case_4')], subject='Test note', body='ces détails envoyés par le client sur le FAX pour la qualité')
|
||||
self.message_post(cr, uid, [ref('crm_case_4')], subject='Test note', body='ces détails envoyés par le client sur le FAX pour la qualité')
|
||||
-
|
||||
I win this opportunity
|
||||
-
|
||||
|
@ -73,7 +73,7 @@
|
|||
I convert mass lead into opportunity customer.
|
||||
-
|
||||
!python {model: crm.lead2opportunity.partner.mass}: |
|
||||
context.update({'active_model': 'crm.lead', 'active_ids': [ref("crm_case_11"), ref("crm_case_2")], 'active_id': ref("crm_case_4")})
|
||||
context.update({'active_model': 'crm.lead', 'active_ids': [ref("crm_case_11"), ref("crm_case_2")], 'active_id': ref("crm_case_11")})
|
||||
id = self.create(cr, uid, {'user_ids': [ref('base.user_root')], 'section_id': ref('crm.section_sales_department')}, context=context)
|
||||
self.mass_convert(cr, uid, [id], context=context)
|
||||
-
|
||||
|
@ -83,7 +83,8 @@
|
|||
opp = self.browse(cr, uid, ref('crm_case_11'))
|
||||
assert opp.name == "Need estimated cost for new project", "Opportunity name not correct"
|
||||
assert opp.type == 'opportunity', 'Lead is not converted to opportunity!'
|
||||
assert opp.partner_id.name == "Thomas Passot", 'Partner mismatch!'
|
||||
expected_partner = "Thomas Passot"
|
||||
assert opp.partner_id.name == expected_partner, 'Partner mismatch! %s vs %s' % (opp.partner_id.name, expected_partner)
|
||||
assert opp.stage_id.id == ref("stage_lead1"), 'Stage of probability is incorrect!'
|
||||
-
|
||||
Then check for second lead converted on opportunity.
|
||||
|
|
|
@ -24,8 +24,6 @@ from tools.translate import _
|
|||
import tools
|
||||
import re
|
||||
|
||||
import time
|
||||
|
||||
class crm_lead2opportunity_partner(osv.osv_memory):
|
||||
_name = 'crm.lead2opportunity.partner'
|
||||
_description = 'Lead To Opportunity Partner'
|
||||
|
@ -35,8 +33,8 @@ class crm_lead2opportunity_partner(osv.osv_memory):
|
|||
'action': fields.selection([('exist', 'Link to an existing partner'), \
|
||||
('create', 'Create a new partner'), \
|
||||
('nothing', 'Do not link to a partner')], \
|
||||
'Action', required=True),
|
||||
'name': fields.selection([('convert', 'Convert to Opportunity'), ('merge', 'Merge with existing Opportunity')],'Select Action', required=True),
|
||||
'Related Partner', required=True),
|
||||
'name': fields.selection([('convert', 'Convert to Opportunities'), ('merge', 'Merge with existing Opportunities')], 'Conversion Action', required=True),
|
||||
'opportunity_ids': fields.many2many('crm.lead', string='Opportunities', domain=[('type', '=', 'opportunity')]),
|
||||
}
|
||||
|
||||
|
@ -68,8 +66,6 @@ class crm_lead2opportunity_partner(osv.osv_memory):
|
|||
if ids:
|
||||
opportunities.append(ids[0])
|
||||
if not partner_id:
|
||||
label = False
|
||||
opp_ids = []
|
||||
if email:
|
||||
# Find email of existing opportunity matches the email_from of the lead
|
||||
cr.execute("""select id from crm_lead where type='opportunity' and
|
||||
|
@ -106,23 +102,36 @@ class crm_lead2opportunity_partner(osv.osv_memory):
|
|||
if context is None:
|
||||
context = {}
|
||||
lead = self.pool.get('crm.lead')
|
||||
partner_id = self._create_partner(cr, uid, ids, context=context)
|
||||
res = False
|
||||
partner_ids_map = self._create_partner(cr, uid, ids, context=context)
|
||||
lead_ids = vals.get('lead_ids', [])
|
||||
user_ids = vals.get('user_ids', False)
|
||||
team_id = vals.get('section_id', False)
|
||||
return lead.convert_opportunity(cr, uid, lead_ids, partner_id, user_ids, team_id, context=context)
|
||||
for lead_id in lead_ids:
|
||||
partner_id = partner_ids_map.get(lead_id, False)
|
||||
# FIXME: cannot pass user_ids as the salesman allocation only works in batch
|
||||
res = lead.convert_opportunity(cr, uid, [lead_id], partner_id, [], team_id, context=context)
|
||||
# FIXME: must perform salesman allocation in batch separately here
|
||||
user_ids = vals.get('user_ids', False)
|
||||
if user_ids:
|
||||
lead.allocate_salesman(cr, uid, lead_ids, user_ids, team_id=team_id, context=context)
|
||||
return res
|
||||
|
||||
def _merge_opportunity(self, cr, uid, ids, opportunity_ids, action='merge', context=None):
|
||||
#TOFIX: is it usefully ?
|
||||
if context is None:
|
||||
context = {}
|
||||
merge_opportunity = self.pool.get('crm.merge.opportunity')
|
||||
res = False
|
||||
#If we convert in mass, don't merge if there is no other opportunity but no warning
|
||||
if action == 'merge' and (len(opportunity_ids) > 1 or not context.get('mass_convert') ):
|
||||
self.write(cr, uid, ids, {'opportunity_ids' : [(6,0, [opportunity_ids[0].id])]}, context=context)
|
||||
context.update({'lead_ids' : record_id, "convert" : True})
|
||||
res = merge_opportunity.merge(cr, uid, data.opportunity_ids, context=context)
|
||||
# Expected: all newly-converted leads (active_ids) will be merged with the opportunity(ies)
|
||||
# that have been selected in the 'opportunity_ids' m2m, with all these records
|
||||
# merged into the first opportunity (and the rest deleted)
|
||||
opportunity_ids = [o.id for o in opportunity_ids]
|
||||
lead_ids = context.get('active_ids', [])
|
||||
if action == 'merge' and lead_ids and opportunity_ids:
|
||||
# Add the leads in the to-merge list, next to other opps
|
||||
# (the fact that they're passed in context['lead_ids'] means that
|
||||
# they cannot be selected to contain the result of the merge.
|
||||
opportunity_ids.extend(lead_ids)
|
||||
context.update({'lead_ids': lead_ids, "convert" : True})
|
||||
res = self.pool.get('crm.lead').merge_opportunity(cr, uid, opportunity_ids, context=context)
|
||||
return res
|
||||
|
||||
def action_apply(self, cr, uid, ids, context=None):
|
||||
|
@ -131,27 +140,37 @@ class crm_lead2opportunity_partner(osv.osv_memory):
|
|||
"""
|
||||
if not context:
|
||||
context = {}
|
||||
|
||||
lead = self.pool.get('crm.lead')
|
||||
lead_ids = context.get('active_ids', [])
|
||||
data = self.browse(cr, uid, ids, context=context)[0]
|
||||
self._convert_opportunity(cr, uid, ids, {'lead_ids': lead_ids}, context=context)
|
||||
self._merge_opportunity(cr, uid, ids, data.opportunity_ids, data.action, context=context)
|
||||
self._merge_opportunity(cr, uid, ids, data.opportunity_ids, data.name, context=context)
|
||||
return lead.redirect_opportunity_view(cr, uid, lead_ids[0], context=context)
|
||||
|
||||
crm_lead2opportunity_partner()
|
||||
|
||||
class crm_lead2opportunity_mass_convert(osv.osv_memory):
|
||||
_name = 'crm.lead2opportunity.partner.mass'
|
||||
_description = 'Mass Lead To Opportunity Partner'
|
||||
_inherit = 'crm.lead2opportunity.partner'
|
||||
|
||||
|
||||
_columns = {
|
||||
'user_ids': fields.many2many('res.users', string='Salesmans'),
|
||||
'user_ids': fields.many2many('res.users', string='Salesmen'),
|
||||
'section_id': fields.many2one('crm.case.section', 'Sales Team'),
|
||||
|
||||
}
|
||||
|
||||
def default_get(self, cr, uid, fields, context=None):
|
||||
res = super(crm_lead2opportunity_mass_convert, self).default_get(cr, uid, fields, context)
|
||||
if 'partner_id' in fields:
|
||||
# avoid forcing the partner of the first lead as default
|
||||
res['partner_id'] = False
|
||||
if 'action' in fields:
|
||||
res['action'] = 'create'
|
||||
if 'name' in fields:
|
||||
res['name'] = 'convert'
|
||||
if 'opportunity_ids' in fields:
|
||||
res['opportunity_ids'] = False
|
||||
return res
|
||||
|
||||
def _convert_opportunity(self, cr, uid, ids, vals, context=None):
|
||||
data = self.browse(cr, uid, ids, context=context)[0]
|
||||
salesteam_id = data.section_id and data.section_id.id or False
|
||||
|
@ -162,9 +181,6 @@ class crm_lead2opportunity_mass_convert(osv.osv_memory):
|
|||
return super(crm_lead2opportunity_mass_convert, self)._convert_opportunity(cr, uid, ids, vals, context=context)
|
||||
|
||||
def mass_convert(self, cr, uid, ids, context=None):
|
||||
value = self.default_get(cr, uid, ['partner_id', 'opportunity_ids'], context=context)
|
||||
value['opportunity_ids'] = [(6, 0, value['opportunity_ids'])]
|
||||
self.write(cr, uid, ids, value, context=context)
|
||||
return self.action_apply(cr, uid, ids, context=context)
|
||||
crm_lead2opportunity_mass_convert()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -37,24 +37,38 @@
|
|||
<field name="model">crm.lead2opportunity.partner.mass</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Convert to Opportunity" version="7.0">
|
||||
<field name="action"/>
|
||||
<field name="name" colspan="4"/>
|
||||
<group string="Assigned Opportunities to">
|
||||
<field name="section_id"/>
|
||||
<group string="Conversion Options">
|
||||
<field name="action"/>
|
||||
<field name="partner_id" attrs="{'invisible':[('action','!=','exist')],'required': [('action', '=', 'exist')]}"/>
|
||||
<field name="name"/>
|
||||
</group>
|
||||
<separator string="Select Salesman"/>
|
||||
<field name="user_ids">
|
||||
<group string="Select Opportunities" attrs="{'invisible': [('name', '=', 'convert')]}">
|
||||
<field name="opportunity_ids" colspan="4" nolabel="1" attrs="{'invisible': [('name', '=', 'convert')]}">
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="name" />
|
||||
<field name="partner_id" />
|
||||
<field name="user_id" />
|
||||
<field name="section_id" />
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
|
||||
<group string="Assign opportunities to">
|
||||
<field name="section_id" />
|
||||
<field name="user_ids" colspan="4">
|
||||
<tree>
|
||||
<field name="name" />
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
|
||||
<footer>
|
||||
<button name="mass_convert" string="Convert into Opportunities" type="object" class="oe_highlight"/>
|
||||
<button name="mass_convert" string="Convert to Opportunities" type="object" class="oe_highlight"/>
|
||||
or
|
||||
<button string="Cancel" class="oe_link" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_crm_lead2opportunity_partner" model="ir.actions.act_window">
|
||||
|
|
|
@ -50,20 +50,20 @@ class crm_lead2partner(osv.osv_memory):
|
|||
def _select_partner(self, cr, uid, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
lead = self.pool.get('crm.lead')
|
||||
partner = self.pool.get('res.partner')
|
||||
lead_ids = list(context and context.get('active_ids', []) or [])
|
||||
if not len(lead_ids):
|
||||
if not context.get('active_model') == 'crm.lead' or not context.get('active_id'):
|
||||
return False
|
||||
this = lead.browse(cr, uid, lead_ids[0], context=context)
|
||||
# Find partner address matches the email_from of the lead
|
||||
res = lead.message_partner_by_email(cr, uid, this.email_from, context=context)
|
||||
partner_id = res.get('partner_id', False)
|
||||
# Find partner name that matches the name of the lead
|
||||
if not partner_id and this.partner_name:
|
||||
partner = self.pool.get('res.partner')
|
||||
lead = self.pool.get('crm.lead')
|
||||
this = lead.browse(cr, uid, context.get('active_id'), context=context)
|
||||
partner_id = False
|
||||
if this.email_from:
|
||||
partner_ids = partner.search(cr, uid, [('email', '=', this.email_from)], context=context)
|
||||
if partner_ids:
|
||||
partner_id = partner_ids[0]
|
||||
if not this.partner_id and this.partner_name:
|
||||
partner_ids = partner.search(cr, uid, [('name', '=', this.partner_name)], context=context)
|
||||
if partner_ids and len(partner_ids):
|
||||
partner_id = partner_ids[0]
|
||||
if partner_ids:
|
||||
partner_id = partner_ids[0]
|
||||
return partner_id
|
||||
|
||||
def default_get(self, cr, uid, fields, context=None):
|
||||
|
@ -107,15 +107,16 @@ class crm_lead2partner(osv.osv_memory):
|
|||
lead_ids = context and context.get('active_ids') or []
|
||||
data = self.browse(cr, uid, ids, context=context)[0]
|
||||
partner_id = data.partner_id and data.partner_id.id or False
|
||||
partner_ids = lead.convert_partner(cr, uid, lead_ids, data.action, partner_id, context=context)
|
||||
return partner_ids[lead_ids[0]]
|
||||
return lead.convert_partner(cr, uid, lead_ids, data.action, partner_id, context=context)
|
||||
|
||||
def make_partner(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
This function Makes partner based on action.
|
||||
"""
|
||||
partner_id = self._create_partner(cr, uid, ids, context=context)
|
||||
return self.pool.get('res.partner').redirect_partner_form(cr, uid, partner_id, context=context)
|
||||
# Only called from Form view, so only meant to convert one Lead.
|
||||
lead_id = context and context.get('active_id') or False
|
||||
partner_ids_map = self._create_partner(cr, uid, ids, context=context)
|
||||
return self.pool.get('res.partner').redirect_partner_form(cr, uid, partner_ids_map.get(lead_id, False), context=context)
|
||||
|
||||
crm_lead2partner()
|
||||
|
||||
|
|
|
@ -57,12 +57,10 @@ class crm_phonecall2partner(osv.osv_memory):
|
|||
if context is None:
|
||||
context = {}
|
||||
phonecall = self.pool.get('crm.phonecall')
|
||||
|
||||
data = self.browse(cr, uid, ids, context=context)[0]
|
||||
call_ids = context and context.get('active_ids') or []
|
||||
partner_id = data.partner_id and data.partner_id.id or False
|
||||
partner_ids = phonecall.convert_partner(cr, uid, call_ids, data.action, partner_id, context=context)
|
||||
return partner_ids[call_ids[0]]
|
||||
return phonecall.convert_partner(cr, uid, call_ids, data.action, partner_id, context=context)
|
||||
|
||||
crm_phonecall2partner()
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ class crm_claim(base_stage, osv.osv):
|
|||
_description = "Claim"
|
||||
_order = "priority,date desc"
|
||||
_inherit = ['mail.thread']
|
||||
_mail_compose_message = True
|
||||
|
||||
_columns = {
|
||||
'id': fields.integer('ID', readonly=True),
|
||||
'name': fields.char('Claim Subject', size=128, required=True),
|
||||
|
@ -194,13 +194,12 @@ class crm_claim(base_stage, osv.osv):
|
|||
if custom_values is None: custom_values = {}
|
||||
custom_values.update({
|
||||
'name': msg.get('subject') or _("No Subject"),
|
||||
'description': msg.get('body_text'),
|
||||
'description': msg.get('body'),
|
||||
'email_from': msg.get('from'),
|
||||
'email_cc': msg.get('cc'),
|
||||
})
|
||||
if msg.get('priority'):
|
||||
custom_values['priority'] = msg.get('priority')
|
||||
custom_values.update(self.message_partner_by_email(cr, uid, msg.get('from'), context=context))
|
||||
return super(crm_claim,self).message_new(cr, uid, msg, custom_values=custom_values, context=context)
|
||||
|
||||
def message_update(self, cr, uid, ids, msg, update_vals=None, context=None):
|
||||
|
@ -220,7 +219,7 @@ class crm_claim(base_stage, osv.osv):
|
|||
'revenue': 'planned_revenue',
|
||||
'probability':'probability'
|
||||
}
|
||||
for line in msg['body_text'].split('\n'):
|
||||
for line in msg['body'].split('\n'):
|
||||
line = line.strip()
|
||||
res = tools.misc.command_re.match(line)
|
||||
if res and maps.get(res.group(1).lower()):
|
||||
|
@ -239,16 +238,16 @@ class crm_claim(base_stage, osv.osv):
|
|||
|
||||
def create_send_note(self, cr, uid, ids, context=None):
|
||||
msg = _('Claim has been <b>created</b>.')
|
||||
return self.message_append_note(cr, uid, ids, body=msg, context=context)
|
||||
return self.message_post(cr, uid, ids, body=msg, context=context)
|
||||
|
||||
def case_refuse_send_note(self, cr, uid, ids, context=None):
|
||||
msg = _('Claim has been <b>refused</b>.')
|
||||
return self.message_append_note(cr, uid, ids, body=msg, context=context)
|
||||
return self.message_post(cr, uid, ids, body=msg, context=context)
|
||||
|
||||
def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
|
||||
""" Override of the (void) default notification method. """
|
||||
stage_name = self.pool.get('crm.claim.stage').name_get(cr, uid, [stage_id], context=context)[0][1]
|
||||
return self.message_append_note(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context)
|
||||
return self.message_post(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context)
|
||||
|
||||
|
||||
class res_partner(osv.osv):
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
-
|
||||
!python {model: crm.claim}: |
|
||||
try:
|
||||
self.message_update(cr, uid,[ref('crm_claim_4')], {'subject': 'Claim Update record','body_text': 'first training session completed',})
|
||||
self.message_update(cr, uid,[ref('crm_claim_4')], {'subject': 'Claim Update record','body': 'first training session completed',})
|
||||
except:
|
||||
pass
|
||||
|
|
@ -38,7 +38,7 @@ class crm_helpdesk(base_state, osv.osv):
|
|||
_description = "Helpdesk"
|
||||
_order = "id desc"
|
||||
_inherit = ['mail.thread']
|
||||
_mail_compose_message = True
|
||||
|
||||
_columns = {
|
||||
'id': fields.integer('ID', readonly=True),
|
||||
'name': fields.char('Name', size=128, required=True),
|
||||
|
@ -105,12 +105,11 @@ class crm_helpdesk(base_state, osv.osv):
|
|||
if custom_values is None: custom_values = {}
|
||||
custom_values.update({
|
||||
'name': msg.get('subject') or _("No Subject"),
|
||||
'description': msg.get('body_text'),
|
||||
'description': msg.get('body'),
|
||||
'email_from': msg.get('from'),
|
||||
'email_cc': msg.get('cc'),
|
||||
'user_id': False,
|
||||
})
|
||||
custom_values.update(self.message_partner_by_email(cr, uid, msg.get('from'), context=context))
|
||||
return super(crm_helpdesk,self).message_new(cr, uid, msg, custom_values=custom_values, context=context)
|
||||
|
||||
def message_update(self, cr, uid, ids, msg, update_vals=None, context=None):
|
||||
|
@ -130,7 +129,7 @@ class crm_helpdesk(base_state, osv.osv):
|
|||
'revenue': 'planned_revenue',
|
||||
'probability':'probability'
|
||||
}
|
||||
for line in msg['body_text'].split('\n'):
|
||||
for line in msg['body'].split('\n'):
|
||||
line = line.strip()
|
||||
res = tools.misc.command_re.match(line)
|
||||
if res and maps.get(res.group(1).lower()):
|
||||
|
@ -149,7 +148,7 @@ class crm_helpdesk(base_state, osv.osv):
|
|||
|
||||
def create_send_note(self, cr, uid, ids, context=None):
|
||||
msg = _('Case has been <b>created</b>.')
|
||||
self.message_append_note(cr, uid, ids, body=msg, context=context)
|
||||
self.message_post(cr, uid, ids, body=msg, context=context)
|
||||
return True
|
||||
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@ class crm_helpdesk_report(osv.osv):
|
|||
c.planned_cost,
|
||||
count(*) as nbr,
|
||||
extract('epoch' from (c.date_closed-c.create_date))/(3600*24) as delay_close,
|
||||
(SELECT count(id) FROM mail_message WHERE model='crm.helpdesk' AND res_id=c.id AND email_from IS NOT NULL) AS email,
|
||||
(SELECT count(id) FROM mail_message WHERE model='crm.helpdesk' AND res_id=c.id AND type = 'email') AS email,
|
||||
abs(avg(extract('epoch' from (c.date_deadline - c.date_closed)))/(3600*24)) as delay_expected
|
||||
from
|
||||
crm_helpdesk c
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
!python {model: crm.helpdesk}: |
|
||||
question_ids = self.search(cr, uid, [('email_from','=', 'Mr. John Right <info@customer.com>')])
|
||||
try:
|
||||
self.message_update(cr, uid, question_ids, {'subject': 'Link of product', 'body_text': 'www.openerp.com'})
|
||||
self.message_update(cr, uid, question_ids, {'subject': 'Link of product', 'body': 'www.openerp.com'})
|
||||
except:
|
||||
pass
|
||||
|
||||
|
|
|
@ -19,8 +19,7 @@
|
|||
attrs="{'invisible':[('partner_assigned_id','=',False)]}"
|
||||
name="%(crm_lead_forward_to_partner_act)d"
|
||||
icon="terp-mail-forward" type="action"
|
||||
context="{'default_name': 'partner', 'default_partner_id': partner_assigned_id}"
|
||||
/>
|
||||
context="{'default_composition_mode': 'forward', 'default_partner_ids': [partner_assigned_id]}"/>
|
||||
</div>
|
||||
</group>
|
||||
<group string="Geo Assignation">
|
||||
|
@ -37,8 +36,6 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
|
||||
<record id="view_crm_opportunity_geo_assign_tree" model="ir.ui.view">
|
||||
<field name="name">crm.lead.geo_assign.tree.inherit</field>
|
||||
<field name="model">crm.lead</field>
|
||||
|
@ -49,6 +46,7 @@
|
|||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="crm_opportunity_partner_filter">
|
||||
<field name="name">crm.opportunity.partner.filter.assigned</field>
|
||||
<field name="model">crm.lead</field>
|
||||
|
@ -63,5 +61,6 @@
|
|||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -72,7 +72,7 @@ msgid "Geo Localize"
|
|||
msgstr ""
|
||||
|
||||
#. module: crm_partner_assign
|
||||
#: help:crm.lead.forward.to.partner,body_text:0
|
||||
#: help:crm.lead.forward.to.partner,body:0
|
||||
msgid "Plain-text version of the message"
|
||||
msgstr ""
|
||||
|
||||
|
@ -129,7 +129,7 @@ msgid "Highest"
|
|||
msgstr ""
|
||||
|
||||
#. module: crm_partner_assign
|
||||
#: field:crm.lead.forward.to.partner,body_text:0
|
||||
#: field:crm.lead.forward.to.partner,body:0
|
||||
msgid "Text contents"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -29,8 +29,8 @@
|
|||
I forward this opportunity to its nearest partner.
|
||||
-
|
||||
!python {model: crm.lead.forward.to.partner}: |
|
||||
context.update({'active_model': 'crm.lead', 'active_id': ref('crm.crm_case_19'), 'active_ids': [ref('crm.crm_case_19')]})
|
||||
forward_id = self.create(cr, uid, {'email_from': 'test@openerp.com', 'send_to': 'partner'}, context=context)
|
||||
context.update({'default_model': 'crm.lead', 'default_res_id': ref('crm.crm_case_19'), 'active_ids': [ref('crm.crm_case_19')]})
|
||||
forward_id = self.create(cr, uid, {}, context=context)
|
||||
try:
|
||||
self.action_forward(cr, uid, [forward_id], context=context)
|
||||
except:
|
||||
|
|
|
@ -20,186 +20,153 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import time
|
||||
import re
|
||||
import time
|
||||
import tools
|
||||
|
||||
from osv import osv, fields
|
||||
from tools.translate import _
|
||||
from mail.mail_message import to_email
|
||||
|
||||
class crm_lead_forward_to_partner(osv.osv_memory):
|
||||
"""Forwards lead history"""
|
||||
""" Forward info history to partners. """
|
||||
_name = 'crm.lead.forward.to.partner'
|
||||
_inherit = "mail.compose.message"
|
||||
|
||||
def default_get(self, cr, uid, fields, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
# set as comment, perform overrided document-like action that calls get_record_data
|
||||
old_mode = context.get('default_composition_mode', 'forward')
|
||||
context['default_composition_mode'] = 'comment'
|
||||
res = super(crm_lead_forward_to_partner, self).default_get(cr, uid, fields, context=context)
|
||||
# back to forward mode
|
||||
context['default_composition_mode'] = old_mode
|
||||
res['composition_mode'] = context['default_composition_mode']
|
||||
return res
|
||||
|
||||
def _get_composition_mode_selection(self, cr, uid, context=None):
|
||||
composition_mode = super(crm_lead_forward_to_partner, self)._get_composition_mode_selection(cr, uid, context=context)
|
||||
composition_mode.append(('forward', 'Forward'))
|
||||
return composition_mode
|
||||
|
||||
_columns = {
|
||||
'send_to': fields.selection([('user', 'User'), ('partner', 'Partner'), \
|
||||
('email', 'Email Address')], 'Send to', required=True),
|
||||
'user_id': fields.many2one('res.users', "User"),
|
||||
'attachment_ids': fields.many2many('ir.attachment','lead_forward_to_partner_attachment_rel', 'wizard_id', 'attachment_id', 'Attachments'),
|
||||
'partner_id' : fields.many2one('res.partner', 'Partner'),
|
||||
'history': fields.selection([('info', 'Case Information'), ('latest', 'Latest email'), ('whole', 'Whole Story')], 'Send history', required=True),
|
||||
'partner_ids': fields.many2many('res.partner',
|
||||
'lead_forward_to_partner_res_partner_rel',
|
||||
'wizard_id', 'partner_id', 'Additional contacts'),
|
||||
'attachment_ids': fields.many2many('ir.attachment',
|
||||
'lead_forward_to_partner_attachment_rel',
|
||||
'wizard_id', 'attachment_id', 'Attachments'),
|
||||
'history_mode': fields.selection([('info', 'Case Information'),
|
||||
('latest', 'Latest email'), ('whole', 'Whole Story')],
|
||||
'Send history', required=True),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'send_to' : 'email',
|
||||
'history': 'latest',
|
||||
'email_from': lambda s, cr, uid, c: s.pool.get('res.users').browse(cr, uid, uid, c).email,
|
||||
'history_mode': 'latest',
|
||||
'content_subtype': lambda self,cr, uid, context={}: 'html',
|
||||
}
|
||||
|
||||
def on_change_email(self, cr, uid, ids, user, context=None):
|
||||
if not user:
|
||||
return {'value': {'email_to': False}}
|
||||
return {'value': {'email_to': self.pool.get('res.users').browse(cr, uid, uid, context=context).email}}
|
||||
|
||||
def on_change_history(self, cr, uid, ids, history_type, context=None):
|
||||
"""Gives message body according to type of history selected
|
||||
* info: Forward the case information
|
||||
* whole: Send the whole history
|
||||
* latest: Send the latest histoy
|
||||
def get_record_data(self, cr, uid, model, res_id, context=None):
|
||||
""" Override of mail.compose.message, to add default values coming
|
||||
form the related lead.
|
||||
"""
|
||||
#TODO: ids and context are not comming
|
||||
res = {}
|
||||
res_id = context.get('active_id')
|
||||
model = context.get('active_model')
|
||||
lead = self.pool.get(model).browse(cr, uid, res_id, context)
|
||||
body_text = self._get_message_body_text(cr, uid, lead, history_type, context=context)
|
||||
if body_text:
|
||||
res = {'value': {'body_text' : body_text}}
|
||||
res = super(crm_lead_forward_to_partner, self).get_record_data(cr, uid, model, res_id, context=context)
|
||||
if model not in ('crm.lead') or not res_id:
|
||||
return res
|
||||
lead_obj = self.pool.get(model)
|
||||
lead = lead_obj.browse(cr, uid, res_id, context=context)
|
||||
subject = '%s: %s - %s' % (_('Fwd'), _('Lead forward'), lead.name)
|
||||
body = self._get_message_body(cr, uid, lead, 'info', context=context)
|
||||
res.update({
|
||||
'subject': subject,
|
||||
'body': body,
|
||||
})
|
||||
return res
|
||||
|
||||
def on_change_partner(self, cr, uid, ids, partner_id):
|
||||
"""This function fills address information based on partner/user selected
|
||||
"""
|
||||
if not partner_id:
|
||||
return {'value' : {'email_to' : False}}
|
||||
partner_obj = self.pool.get('res.partner')
|
||||
data = {}
|
||||
partner = partner_obj.browse(cr, uid, [partner_id])
|
||||
user_id = partner and partner[0].user_id or False
|
||||
data.update({'email_from': partner and partner[0].email or "",
|
||||
'email_cc' : user_id and user_id.user or '',
|
||||
'user_id': user_id and user_id.id or False})
|
||||
return {'value' : data}
|
||||
def on_change_history_mode(self, cr, uid, ids, history_mode, model, res_id, context=None):
|
||||
""" Update body when changing history_mode """
|
||||
if model and model == 'crm.lead' and res_id:
|
||||
lead = self.pool.get(model).browse(cr, uid, res_id, context=context)
|
||||
body = self._get_message_body(cr, uid, lead, history_mode, context=context)
|
||||
return {'value': {'body': body}}
|
||||
|
||||
def create(self, cr, uid, values, context=None):
|
||||
""" TDE-HACK: remove 'type' from context, because when viewing an
|
||||
opportunity form view, a default_type is set and propagated
|
||||
to the wizard, that has a not matching type field. """
|
||||
default_type = context.pop('default_type', None)
|
||||
new_id = super(crm_lead_forward_to_partner, self).create(cr, uid, values, context=context)
|
||||
if default_type:
|
||||
context['default_type'] = default_type
|
||||
return new_id
|
||||
|
||||
def action_forward(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
Forward the lead to a partner
|
||||
"""
|
||||
""" Forward the lead to a partner """
|
||||
if context is None:
|
||||
context = {}
|
||||
res = {'type': 'ir.actions.act_window_close'}
|
||||
model = context.get('active_model')
|
||||
if model not in ('crm.lead'):
|
||||
wizard = self.browse(cr, uid, ids[0], context=context)
|
||||
if wizard.model not in ('crm.lead'):
|
||||
return res
|
||||
|
||||
this = self.browse(cr, uid, ids[0], context=context)
|
||||
lead = self.pool.get(model)
|
||||
lead_id = context and context.get('active_id', False) or False
|
||||
lead_ids = lead_id and [lead_id] or []
|
||||
mode = context.get('mail.compose.message.mode')
|
||||
if mode == 'mass_mail':
|
||||
lead = self.pool.get(wizard.model)
|
||||
lead_ids = wizard.res_id and [wizard.res_id] or []
|
||||
|
||||
if wizard.composition_mode == 'mass_mail':
|
||||
lead_ids = context and context.get('active_ids', []) or []
|
||||
value = self.default_get(cr, uid, ['body_text', 'email_to', 'email_cc', 'subject', 'history'], context=context)
|
||||
value = self.default_get(cr, uid, ['body', 'email_to', 'email_cc', 'subject', 'history_mode'], context=context)
|
||||
self.write(cr, uid, ids, value, context=context)
|
||||
context['mail.compose.message.mode'] = mode
|
||||
|
||||
self.send_mail(cr, uid, ids, context=context)
|
||||
for case in lead.browse(cr, uid, lead_ids, context=context):
|
||||
if (this.send_to == 'partner' and this.partner_id):
|
||||
lead.assign_partner(cr, uid, [case.id], this.partner_id.id, context=context)
|
||||
# for case in lead.browse(cr, uid, lead_ids, context=context):
|
||||
# TODO: WHAT TO DO WITH THAT ?
|
||||
# if (this.send_to == 'partner' and this.partner_id):
|
||||
# lead.assign_partner(cr, uid, [case.id], this.partner_id.id, context=context)
|
||||
|
||||
if this.send_to == 'user':
|
||||
lead.allocate_salesman(cr, uid, [case.id], [this.user_id.id], context=context)
|
||||
|
||||
email_cc = to_email(case.email_cc)
|
||||
email_cc = email_cc and email_cc[0] or ''
|
||||
new_cc = []
|
||||
if email_cc:
|
||||
new_cc.append(email_cc)
|
||||
for to in this.email_to.split(','):
|
||||
email_to = to_email(to)
|
||||
email_to = email_to and email_to[0] or ''
|
||||
if email_to not in new_cc:
|
||||
new_cc.append(to)
|
||||
update_vals = {'email_cc' : ', '.join(new_cc) }
|
||||
lead.write(cr, uid, [case.id], update_vals, context=context)
|
||||
# if this.send_to == 'user':
|
||||
# lead.allocate_salesman(cr, uid, [case.id], [this.user_id.id], context=context)
|
||||
return res
|
||||
|
||||
def _get_info_body_text(self, cr, uid, lead, context=None):
|
||||
def _get_info_body(self, cr, uid, lead, context=None):
|
||||
field_names = []
|
||||
proxy = self.pool.get(lead._name)
|
||||
if lead.type == 'opportunity':
|
||||
field_names += ['partner_id']
|
||||
field_names += [
|
||||
'partner_name' , 'title', 'function', 'street', 'street2',
|
||||
'partner_name' , 'title', 'function', 'street', 'street2',
|
||||
'zip', 'city', 'country_id', 'state_id', 'email_from',
|
||||
'phone', 'fax', 'mobile', 'categ_id', 'description',
|
||||
]
|
||||
return proxy._mail_body_text(cr, uid, lead, field_names, context=context)
|
||||
return proxy._mail_body(cr, uid, lead, field_names, context=context)
|
||||
|
||||
def _get_message_body_text(self, cr, uid, lead, mode='whole', context=None):
|
||||
"""This function gets whole communication history and returns as top posting style
|
||||
def _get_message_body(self, cr, uid, lead, history_mode='whole', context=None):
|
||||
""" This function gets whole communication history and returns as top
|
||||
posting style
|
||||
#1: form a body, based on the lead
|
||||
#2: append to the body the communication history, based on the
|
||||
history_mode parameter
|
||||
|
||||
- info: Forward the case information
|
||||
- latest: Send the latest history
|
||||
- whole: Send the whole history
|
||||
|
||||
:param lead: browse_record on crm.lead
|
||||
:param history_mode: 'whole' or 'latest'
|
||||
"""
|
||||
mail_message = self.pool.get('mail.message')
|
||||
message_ids = []
|
||||
body = self._get_info_body_text(cr, uid, lead, context=context)
|
||||
if mode in ('whole', 'latest'):
|
||||
message_ids = lead.message_ids
|
||||
message_ids = map(lambda x: x.id, filter(lambda x: x.email_from, message_ids))
|
||||
if mode == 'latest' and len(message_ids):
|
||||
message_ids = [message_ids[0]]
|
||||
for message in mail_message.browse(cr, uid, message_ids, context=context):
|
||||
header = '-------- Original Message --------'
|
||||
sender = 'From: %s' %(message.email_from or '')
|
||||
to = 'To: %s' % (message.email_to or '')
|
||||
sentdate = 'Date: %s' % (message.date or '')
|
||||
desc = '\n%s'%(message.body_text)
|
||||
original = [header, sender, to, sentdate, desc, '\n']
|
||||
original = '\n'.join(original)
|
||||
body += original
|
||||
body = self._get_info_body(cr, uid, lead, context=context)
|
||||
if history_mode not in ('whole', 'latest'):
|
||||
return body or ''
|
||||
for message in lead.message_ids:
|
||||
header = '-------- Original Message --------'
|
||||
sentdate = 'Date: %s' % (message.date or '')
|
||||
desc = '\n%s'%(message.body)
|
||||
original = [header, sentdate, desc, '\n']
|
||||
original = '\n'.join(original)
|
||||
body += original
|
||||
if history_mode == 'latest':
|
||||
break
|
||||
return body or ''
|
||||
|
||||
def get_value(self, cr, uid, model, res_id, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
res = super(crm_lead_forward_to_partner, self).get_value(cr, uid, model, res_id, context=context)
|
||||
if model not in ("crm.lead"):
|
||||
return res
|
||||
proxy = self.pool.get(model)
|
||||
partner = self.pool.get('res.partner')
|
||||
lead = proxy.browse(cr, uid, res_id, context=context)
|
||||
mode = context.get('mail.compose.message.mode')
|
||||
if mode == "forward":
|
||||
body_type = context.get('mail.compose.message.body')
|
||||
email_cc = res.get('email_cc', "")
|
||||
email = res.get('email_to', "")
|
||||
subject = '%s: %s - %s' % (_('Fwd'), 'Lead forward', lead.name)
|
||||
body = self._get_message_body_text(cr, uid, lead, body_type, context=context)
|
||||
partner_assigned_id = lead.partner_assigned_id and lead.partner_assigned_id.id or False
|
||||
user_id = False
|
||||
if not partner_assigned_id:
|
||||
partner_assigned_id = proxy.search_geo_partner(cr, uid, [lead.id], context=None).get(lead.id, False)
|
||||
if partner_assigned_id:
|
||||
assigned_partner = partner.browse(cr, uid, partner_assigned_id, context=context)
|
||||
user_id = assigned_partner.user_id and assigned_partner.user_id.id or False
|
||||
email_cc = assigned_partner.user_id and assigned_partner.user_id.email or ''
|
||||
email = assigned_partner.email
|
||||
|
||||
res.update({
|
||||
'subject' : subject,
|
||||
'body_text' : body,
|
||||
'email_cc' : email_cc,
|
||||
'email_to' : email,
|
||||
'partner_assigned_id': partner_assigned_id,
|
||||
'user_id': user_id,
|
||||
})
|
||||
return res
|
||||
|
||||
|
||||
def default_get(self, cr, uid, fields, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
context['mail.compose.message.mode'] = 'forward'
|
||||
context['mail.compose.message.body'] = 'info'
|
||||
return super(crm_lead_forward_to_partner, self).default_get(cr, uid, fields, context=context)
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -7,42 +7,31 @@
|
|||
<field name="model">crm.lead.forward.to.partner</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Send Mail" version="7.0">
|
||||
<field name="composition_mode" colspan="2" nolabel="1" invisible="1"/>
|
||||
<field name="model" colspan="2" nolabel="1" invisible="1"/>
|
||||
<field name="res_id" colspan="2" nolabel="1" invisible="1"/>
|
||||
<separator string="Forward to Partner" colspan="4"/>
|
||||
<group col="4" colspan="6">
|
||||
<field name="history" colspan="2" on_change="on_change_history(history, context)"/>
|
||||
<field name="send_to" colspan="2"/>
|
||||
<group col="2" colspan="2" attrs="{ 'invisible' : [('send_to','!=','user')]}">
|
||||
<field name="user_id"
|
||||
attrs="{ 'required' : [('send_to','=','user')]}"
|
||||
on_change="on_change_email(user_id)"/>
|
||||
</group>
|
||||
<group col="4" colspan="4" attrs="{'invisible' : [('send_to','!=','partner')]}">
|
||||
<field name="partner_id" attrs="{'required' : [('send_to','=','partner')]}" on_change="on_change_partner(partner_id)" colspan="2"/>
|
||||
</group>
|
||||
<group col="4">
|
||||
<field name="history_mode" colspan="4"
|
||||
on_change="on_change_history_mode(history_mode, model, res_id)"/>
|
||||
<field name="subject" colspan="4"/>
|
||||
<field name="partner_ids" colspan="4" widget="many2many_tags"
|
||||
on_change="onchange_partner_ids(partner_ids)"/>
|
||||
<notebook colspan="4">
|
||||
<page string="Body">
|
||||
<field name="body"/>
|
||||
</page>
|
||||
<page string="Attachments">
|
||||
<field name="attachment_ids"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</group>
|
||||
<separator string="" colspan="4"/>
|
||||
<group col="6" colspan="4">
|
||||
<field name="email_from" colspan="4" required="1"/>
|
||||
<field name="email_to" colspan="4" required="1"/>
|
||||
<field name="email_cc" colspan="4"/>
|
||||
<field name="email_bcc" colspan="4"/>
|
||||
<field name="reply_to" colspan="4"/>
|
||||
<field name="subject" colspan="4" widget="char" size="512"/>
|
||||
</group>
|
||||
<separator string="" colspan="4"/>
|
||||
<notebook colspan="4">
|
||||
<page string="Body">
|
||||
<field name="body_text" colspan="4" nolabel="1"/>
|
||||
</page>
|
||||
<page string="Attachments">
|
||||
<label string="Add here all attachments of the current document you want to include in the Email." colspan="4"/>
|
||||
<field name="attachment_ids" colspan="4" nolabel="1"/>
|
||||
</page>
|
||||
</notebook>
|
||||
<footer>
|
||||
<button name="action_forward" string="Send" type="object" class="oe_highlight"/>
|
||||
<button name="action_forward" string="Send" type="object"
|
||||
class="oe_highlight"/>
|
||||
or
|
||||
<button string="Cancel" class="oe_link" special="cancel"/>
|
||||
<button string="Cancel" special="cancel"
|
||||
class="oe_link"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
|
@ -57,18 +46,14 @@
|
|||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
|
||||
|
||||
<act_window id="action_crm_send_mass_forward"
|
||||
multi="True"
|
||||
key2="client_action_multi" name="Mass forward to partner"
|
||||
res_model="crm.lead.forward.to.partner" src_model="crm.lead"
|
||||
view_mode="form" target="new" view_type="form"
|
||||
context="{'mail.compose.message.mode' : 'mass_mail'}"
|
||||
context="{'default_composition_mode' : 'mass_mail'}"
|
||||
view_id="crm_lead_forward_to_partner_form"
|
||||
/>
|
||||
|
||||
|
||||
/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -28,6 +28,8 @@ from osv import osv
|
|||
from osv import fields
|
||||
import tools
|
||||
from tools.translate import _
|
||||
from tools.html_sanitize import html_sanitize
|
||||
from tools import append_content_to_html
|
||||
from urllib import quote as quote
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -38,10 +40,8 @@ except ImportError:
|
|||
|
||||
class email_template(osv.osv):
|
||||
"Templates for sending email"
|
||||
_inherit = 'mail.message'
|
||||
_name = "email.template"
|
||||
_description = 'Email Templates'
|
||||
_rec_name = 'name' # override mail.message's behavior
|
||||
|
||||
def render_template(self, cr, uid, template, model, res_id, context=None):
|
||||
"""Render the given template text, replace mako expressions ``${expr}``
|
||||
|
@ -99,66 +99,46 @@ class email_template(osv.osv):
|
|||
mod_name = False
|
||||
if model_id:
|
||||
mod_name = self.pool.get('ir.model').browse(cr, uid, model_id, context).model
|
||||
return {'value':{'model': mod_name}}
|
||||
|
||||
def name_get(self, cr, uid, ids, context=None):
|
||||
""" Override name_get of mail.message: return directly the template
|
||||
name, and not the generated name from mail.message.common."""
|
||||
return [(record.id, record.name) for record in self.browse(cr, uid, ids, context=context)]
|
||||
return {'value': {'model': mod_name}}
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=250),
|
||||
'model_id': fields.many2one('ir.model', 'Related document model'),
|
||||
'lang': fields.char('Language Selection', size=250,
|
||||
'name': fields.char('Name'),
|
||||
'model_id': fields.many2one('ir.model', 'Applies to', help="The kind of document with with this template can be used"),
|
||||
'model': fields.related('model_id', 'model', type='char', string='Related Document Model',
|
||||
size=128, select=True, store=True, readonly=True),
|
||||
'lang': fields.char('Language',
|
||||
help="Optional translation language (ISO code) to select when sending out an email. "
|
||||
"If not set, the english version will be used. "
|
||||
"This should usually be a placeholder expression "
|
||||
"that provides the appropriate language code, e.g. "
|
||||
"${object.partner_id.lang.code}."),
|
||||
"${object.partner_id.lang.code}.",
|
||||
placeholder="${object.partner_id.lang.code}"),
|
||||
'user_signature': fields.boolean('Add Signature',
|
||||
help="If checked, the user's signature will be appended to the text version "
|
||||
"of the message"),
|
||||
'report_name': fields.char('Report Filename', size=200, translate=True,
|
||||
help="Name to use for the generated report file (may contain placeholders)\n"
|
||||
"The extension can be omitted and will then come from the report type."),
|
||||
'report_template':fields.many2one('ir.actions.report.xml', 'Optional report to print and attach'),
|
||||
'ref_ir_act_window':fields.many2one('ir.actions.act_window', 'Sidebar action', readonly=True,
|
||||
help="Sidebar action to make this template available on records "
|
||||
"of the related document model"),
|
||||
'ref_ir_value':fields.many2one('ir.values', 'Sidebar Button', readonly=True,
|
||||
help="Sidebar button to open the sidebar action"),
|
||||
'track_campaign_item': fields.boolean('Resource Tracking',
|
||||
help="Enable this is you wish to include a special tracking marker "
|
||||
"in outgoing emails so you can identify replies and link "
|
||||
"them back to the corresponding resource record. "
|
||||
"This is useful for CRM leads for example"),
|
||||
|
||||
# Overridden mail.message.common fields for technical reasons:
|
||||
'model': fields.related('model_id','model', type='char', string='Related Document Model',
|
||||
size=128, select=True, store=True, readonly=True),
|
||||
# we need a separate m2m table to avoid ID collisions with the original mail.message entries
|
||||
'attachment_ids': fields.many2many('ir.attachment', 'email_template_attachment_rel', 'email_template_id',
|
||||
'attachment_id', 'Files to attach',
|
||||
help="You may attach files to this template, to be added to all "
|
||||
"emails created from this template"),
|
||||
|
||||
# Overridden mail.message.common fields to make tooltips more appropriate:
|
||||
'subject':fields.char('Subject', size=512, translate=True, help="Subject (placeholders may be used here)",),
|
||||
'email_from': fields.char('From', size=128, help="Sender address (placeholders may be used here)"),
|
||||
'email_to': fields.char('To', size=256, help="Comma-separated recipient addresses (placeholders may be used here)"),
|
||||
'email_cc': fields.char('Cc', size=256, help="Carbon copy recipients (placeholders may be used here)"),
|
||||
'email_bcc': fields.char('Bcc', size=256, help="Blind carbon copy recipients (placeholders may be used here)"),
|
||||
'reply_to': fields.char('Reply-To', size=250, help="Preferred response address (placeholders may be used here)"),
|
||||
'subject': fields.char('Subject', translate=True, help="Subject (placeholders may be used here)",),
|
||||
'email_from': fields.char('From', help="Sender address (placeholders may be used here)"),
|
||||
'email_to': fields.char('To', help="Comma-separated recipient addresses (placeholders may be used here)"),
|
||||
'email_cc': fields.char('Cc', help="Carbon copy recipients (placeholders may be used here)"),
|
||||
'reply_to': fields.char('Reply-To', help="Preferred response address (placeholders may be used here)"),
|
||||
'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing Mail Server', readonly=False,
|
||||
help="Optional preferred server for outgoing mails. If not set, the highest "
|
||||
"priority one will be used."),
|
||||
'body_text': fields.text('Text Contents', translate=True, help="Plaintext version of the message (placeholders may be used here)"),
|
||||
'body_html': fields.text('Rich-text Contents', translate=True, help="Rich-text/HTML version of the message (placeholders may be used here)"),
|
||||
'message_id': fields.char('Message-Id', size=256, help="Message-ID SMTP header to use in outgoing messages based on this template. "
|
||||
"Please note that this overrides the 'Resource Tracking' option, "
|
||||
"so if you simply need to track replies to outgoing emails, enable "
|
||||
"that option instead.\n"
|
||||
"Placeholders must be used here, as this value always needs to be unique!"),
|
||||
'body_html': fields.text('Body', translate=True, help="Rich-text/HTML version of the message (placeholders may be used here)"),
|
||||
'report_name': fields.char('Report Filename', translate=True,
|
||||
help="Name to use for the generated report file (may contain placeholders)\n"
|
||||
"The extension can be omitted and will then come from the report type."),
|
||||
'report_template': fields.many2one('ir.actions.report.xml', 'Optional report to print and attach'),
|
||||
'ref_ir_act_window': fields.many2one('ir.actions.act_window', 'Sidebar action', readonly=True,
|
||||
help="Sidebar action to make this template available on records "
|
||||
"of the related document model"),
|
||||
'ref_ir_value': fields.many2one('ir.values', 'Sidebar Button', readonly=True,
|
||||
help="Sidebar button to open the sidebar action"),
|
||||
'attachment_ids': fields.many2many('ir.attachment', 'email_template_attachment_rel', 'email_template_id',
|
||||
'attachment_id', 'Attachments',
|
||||
help="You may attach files to this template, to be added to all "
|
||||
"emails created from this template"),
|
||||
'auto_delete': fields.boolean('Auto Delete', help="Permanently delete this email after sending it, to save space"),
|
||||
|
||||
# Fake fields used to implement the placeholder assistant
|
||||
'model_object_field': fields.many2one('ir.model.fields', string="Field",
|
||||
|
@ -172,12 +152,12 @@ class email_template(osv.osv):
|
|||
help="When a relationship field is selected as first field, "
|
||||
"this field lets you select the target field within the "
|
||||
"destination document model (sub-model)."),
|
||||
'null_value': fields.char('Null value', help="Optional value to use if the target field is empty", size=128),
|
||||
'copyvalue': fields.char('Expression', size=256, help="Final placeholder expression, to be copy-pasted in the desired template field."),
|
||||
'null_value': fields.char('Default Value', help="Optional value to use if the target field is empty"),
|
||||
'copyvalue': fields.char('Placeholder Expression', help="Final placeholder expression, to be copy-pasted in the desired template field."),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'track_campaign_item': True
|
||||
'auto_delete': True,
|
||||
}
|
||||
|
||||
def create_action(self, cr, uid, ids, context=None):
|
||||
|
@ -195,7 +175,7 @@ class email_template(osv.osv):
|
|||
'res_model': 'mail.compose.message',
|
||||
'src_model': src_obj,
|
||||
'view_type': 'form',
|
||||
'context': "{'mail.compose.message.mode':'mass_mail', 'mail.compose.template_id' : %d}" % (template.id),
|
||||
'context': "{'default_composition_mode': 'mass_mail', 'default_template_id' : %d}" % (template.id),
|
||||
'view_mode':'form,tree',
|
||||
'view_id': res_id,
|
||||
'target': 'new',
|
||||
|
@ -222,7 +202,7 @@ class email_template(osv.osv):
|
|||
if template.ref_ir_value:
|
||||
ir_values_obj = self.pool.get('ir.values')
|
||||
ir_values_obj.unlink(cr, uid, template.ref_ir_value.id, context)
|
||||
except:
|
||||
except Exception:
|
||||
raise osv.except_osv(_("Warning"), _("Deletion of the action record failed."))
|
||||
return True
|
||||
|
||||
|
@ -294,65 +274,33 @@ class email_template(osv.osv):
|
|||
:param res_id: id of the record to use for rendering the template (model
|
||||
is taken from template definition)
|
||||
:returns: a dict containing all relevant fields for creating a new
|
||||
mail.message entry, with the addition one additional
|
||||
special key ``attachments`` containing a list of
|
||||
mail.mail entry, with one extra key ``attachments``, in the
|
||||
format expected by :py:meth:`mail_thread.message_post`.
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
values = {
|
||||
'subject': False,
|
||||
'body_text': False,
|
||||
'body_html': False,
|
||||
'email_from': False,
|
||||
'email_to': False,
|
||||
'email_cc': False,
|
||||
'email_bcc': False,
|
||||
'reply_to': False,
|
||||
'auto_delete': False,
|
||||
'model': False,
|
||||
'res_id': False,
|
||||
'mail_server_id': False,
|
||||
'attachments': False,
|
||||
'attachment_ids': False,
|
||||
'message_id': False,
|
||||
'state': 'outgoing',
|
||||
'content_subtype': 'plain',
|
||||
'partner_ids': [],
|
||||
}
|
||||
if not template_id:
|
||||
return values
|
||||
|
||||
report_xml_pool = self.pool.get('ir.actions.report.xml')
|
||||
template = self.get_email_template(cr, uid, template_id, res_id, context)
|
||||
|
||||
for field in ['subject', 'body_text', 'body_html', 'email_from',
|
||||
'email_to', 'email_cc', 'email_bcc', 'reply_to',
|
||||
'message_id']:
|
||||
values = {}
|
||||
for field in ['subject', 'body_html', 'email_from',
|
||||
'email_to', 'email_cc', 'reply_to']:
|
||||
values[field] = self.render_template(cr, uid, getattr(template, field),
|
||||
template.model, res_id, context=context) \
|
||||
or False
|
||||
|
||||
# if email_to: find or create a partner
|
||||
if values['email_to']:
|
||||
partner_id = self.pool.get('mail.thread').message_partner_by_email(cr, uid, values['email_to'], context=context)['partner_id']
|
||||
if not partner_id:
|
||||
partner_id = self.pool.get('res.partner').name_create(cr, uid, values['email_to'], context=context)
|
||||
values['partner_ids'] = [partner_id]
|
||||
|
||||
if values['body_html']:
|
||||
values.update(content_subtype='html')
|
||||
|
||||
if template.user_signature:
|
||||
signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
|
||||
values['body_text'] += '\n\n' + signature
|
||||
values['body_html'] = append_content_to_html(values['body_html'], signature)
|
||||
|
||||
values.update(mail_server_id = template.mail_server_id.id or False,
|
||||
auto_delete = template.auto_delete,
|
||||
if values['body_html']:
|
||||
values['body'] = html_sanitize(values['body_html'])
|
||||
|
||||
values.update(mail_server_id=template.mail_server_id.id or False,
|
||||
auto_delete=template.auto_delete,
|
||||
model=template.model,
|
||||
res_id=res_id or False)
|
||||
|
||||
attachments = {}
|
||||
# Add report as a Document
|
||||
attachments = []
|
||||
# Add report in attachments
|
||||
if template.report_template:
|
||||
report_name = self.render_template(cr, uid, template.report_name, template.model, res_id, context=context)
|
||||
report_service = 'report.' + report_xml_pool.browse(cr, uid, template.report_template.id, context).report_name
|
||||
|
@ -368,12 +316,11 @@ class email_template(osv.osv):
|
|||
ext = "." + format
|
||||
if not report_name.endswith(ext):
|
||||
report_name += ext
|
||||
attachments[report_name] = result
|
||||
attachments.append(report_name, result)
|
||||
|
||||
# Add document attachments
|
||||
# Add template attachments
|
||||
for attach in template.attachment_ids:
|
||||
# keep the bytes as fetched from the db, base64 encoded
|
||||
attachments[attach.datas_fname] = attach.datas
|
||||
attachments.append((attach.datas_fname, attach.datas))
|
||||
|
||||
values['attachments'] = attachments
|
||||
return values
|
||||
|
@ -391,12 +338,12 @@ class email_template(osv.osv):
|
|||
:returns: id of the mail.message that was created
|
||||
"""
|
||||
if context is None: context = {}
|
||||
mail_message = self.pool.get('mail.message')
|
||||
mail_mail = self.pool.get('mail.mail')
|
||||
ir_attachment = self.pool.get('ir.attachment')
|
||||
values = self.generate_email(cr, uid, template_id, res_id, context=context)
|
||||
assert 'email_from' in values, 'email_from is missing or empty after template rendering, send_mail() cannot proceed'
|
||||
attachments = values.pop('attachments') or {}
|
||||
msg_id = mail_message.create(cr, uid, values, context=context)
|
||||
msg_id = mail_mail.create(cr, uid, values, context=context)
|
||||
# link attachments
|
||||
attachment_ids = []
|
||||
for fname, fcontent in attachments.iteritems():
|
||||
|
@ -404,15 +351,15 @@ class email_template(osv.osv):
|
|||
'name': fname,
|
||||
'datas_fname': fname,
|
||||
'datas': fcontent,
|
||||
'res_model': mail_message._name,
|
||||
'res_model': mail_mail._name,
|
||||
'res_id': msg_id,
|
||||
}
|
||||
context.pop('default_type', None)
|
||||
attachment_ids.append(ir_attachment.create(cr, uid, attachment_data, context=context))
|
||||
if attachment_ids:
|
||||
mail_message.write(cr, uid, msg_id, {'attachment_ids': [(6, 0, attachment_ids)]}, context=context)
|
||||
mail_mail.write(cr, uid, msg_id, {'attachment_ids': [(6, 0, attachment_ids)]}, context=context)
|
||||
if force_send:
|
||||
mail_message.send(cr, uid, [msg_id], context=context)
|
||||
mail_mail.send(cr, uid, [msg_id], context=context)
|
||||
return msg_id
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -1,99 +1,67 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record model="ir.ui.view" id="email_template_form">
|
||||
<field name="name">email.template.form</field>
|
||||
<field name="model">email.template</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Templates" version="7.0">
|
||||
<sheet>
|
||||
<group col="4">
|
||||
<field name="name" required="1"/>
|
||||
<field name="model_id" required="1" on_change="onchange_model_id(model_id)"/>
|
||||
<field name="model" invisible="1"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Email Details">
|
||||
<group>
|
||||
<field name="email_from" required="1"/>
|
||||
<field name="email_to" required="1"/>
|
||||
<field name="email_cc"/>
|
||||
<field name="email_bcc"/>
|
||||
<field name="reply_to"/>
|
||||
<field name="subject" required="1"/>
|
||||
<notebook colspan="4">
|
||||
<page string="Body (Text)">
|
||||
<field name="body_text" colspan="4" width="250" height="250" nolabel="1"/>
|
||||
</page>
|
||||
<page string="Body (Rich/HTML)">
|
||||
<field name="body_html" colspan="4" width="250" height="250" nolabel="1"/>
|
||||
<label string="Note: This is Raw HTML." colspan="4"/>
|
||||
</page>
|
||||
</notebook>
|
||||
<field name="user_signature"/>
|
||||
<notebook colspan="4">
|
||||
<page string="Dynamic Values Builder">
|
||||
<group>
|
||||
<field name="model_object_field"
|
||||
domain="[('model_id','=',model_id),('ttype','!=','one2many'),('ttype','!=','many2many')]"
|
||||
on_change="onchange_sub_model_object_value_field(model_object_field)"/>
|
||||
<field name="sub_object" readonly="1"/>
|
||||
<field name="sub_model_object_field"
|
||||
domain="[('model_id','=',sub_object),('ttype','!=','one2many'),('ttype','!=','many2many')]"
|
||||
attrs="{'readonly':[('sub_object','=',False)],'required':[('sub_object','!=',False)]}"
|
||||
on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field)"/>
|
||||
<field name="null_value" on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field,null_value)"/>
|
||||
<field name="copyvalue"/>
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/><h1><field name="name" required="1"/></h1>
|
||||
<h3><label for="model_id"/><field name="model_id" required="1" on_change="onchange_model_id(model_id)" class="oe_inline"/></h3>
|
||||
<field name="model" invisible="1"/>
|
||||
</div>
|
||||
<div class="oe_right oe_button_box" name="buttons">
|
||||
<field name="ref_ir_act_window" invisible="1"/>
|
||||
<button name="create_action" string="Add context action" type="object"
|
||||
attrs="{'invisible':[('ref_ir_act_window','!=',False)]}"
|
||||
help="Display an option on related documents to open a composition wizard with this template"/>
|
||||
<button name="unlink_action" string="Remove context action" type="object"
|
||||
attrs="{'invisible':[('ref_ir_act_window','=',False)]}"
|
||||
help="Remove the contextual action to use this template on related documents"/>
|
||||
<button name="%(wizard_email_template_preview)d" string="Preview"
|
||||
type="action" target="new"
|
||||
context="{'template_id':active_id}"/>
|
||||
</div>
|
||||
<notebook>
|
||||
<page string="Email Details">
|
||||
<group>
|
||||
<group string="Addressing">
|
||||
<field name="email_from" required="1"/>
|
||||
<field name="email_to" required="1"/>
|
||||
<field name="email_cc"/>
|
||||
<field name="reply_to"/>
|
||||
<field name="user_signature"/>
|
||||
</group>
|
||||
<group string="Dynamic Value Builder" class="oe_edit_only">
|
||||
<field name="model_object_field" domain="[('model_id','=',model_id),('ttype','!=','one2many'),('ttype','!=','many2many')]" on_change="onchange_sub_model_object_value_field(model_object_field)"/>
|
||||
<field name="sub_object" readonly="1"/>
|
||||
<field name="sub_model_object_field" domain="[('model_id','=',sub_object),('ttype','!=','one2many'),('ttype','!=','many2many')]" attrs="{'readonly':[('sub_object','=',False)],'required':[('sub_object','!=',False)]}" on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field)"/>
|
||||
<field name="null_value" on_change="onchange_sub_model_object_value_field(model_object_field,sub_model_object_field,null_value)"/>
|
||||
<field name="copyvalue"/>
|
||||
</group>
|
||||
<group string="Contents" colspan="2">
|
||||
<field name="subject" required="1"/>
|
||||
<field name="body_html" width="250" height="450" nolabel="1" colspan="2" placeholder="Email contents (in raw HTML format)"/>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
<button name="%(wizard_email_template_preview)d" string="Preview Template"
|
||||
type="action" colspan="4" target="new" icon="gtk-zoom-fit" context="{'template_id':active_id}"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Advanced">
|
||||
<group colspan="2" col="2">
|
||||
<group colspan="2" col="2">
|
||||
<separator string="Sidebar Button" colspan="2"/>
|
||||
<button name="create_action" string="Add sidebar button" type="object" icon="gtk-execute"
|
||||
colspan="2" attrs="{'invisible':[('ref_ir_act_window','!=',False)]}"
|
||||
help="Display a button in the sidebar of related documents to open a composition wizard with this template"
|
||||
/>
|
||||
<field name="ref_ir_act_window" attrs="{'invisible':[('ref_ir_act_window','=',False)]}"/>
|
||||
<field name="ref_ir_value" attrs="{'invisible':[('ref_ir_act_window','=',False)]}"/>
|
||||
<button name="unlink_action" string="Remove sidebar button" type="object" icon="gtk-delete"
|
||||
colspan="2" attrs="{'invisible':[('ref_ir_act_window','=',False)]}"
|
||||
help="Remove the sidebar button currently displayed on related documents"
|
||||
/>
|
||||
</group>
|
||||
<group colspan="2" col="2">
|
||||
<separator string="Advanced Options" colspan="2"/>
|
||||
<field name="mail_server_id"/>
|
||||
<field name="track_campaign_item"/>
|
||||
<field name="message_id"/>
|
||||
<field name="auto_delete"/>
|
||||
</page>
|
||||
<page string="Advanced">
|
||||
<group>
|
||||
<field name="lang"/>
|
||||
<field name="mail_server_id"/>
|
||||
<field name="auto_delete"/>
|
||||
<field name="report_template" domain="[('model','=',model)]"/>
|
||||
<field name="report_name" class="oe_inline"
|
||||
attrs="{'invisible':[('report_template','=',False)]}"/>
|
||||
<field name="attachment_ids">
|
||||
<tree><field name="name"/></tree>
|
||||
</field>
|
||||
</group>
|
||||
</group>
|
||||
<group colspan="2" col="2">
|
||||
<separator string="Attachments" colspan="2"/>
|
||||
<notebook colspan="2">
|
||||
<page string="Attach Report">
|
||||
<group>
|
||||
<field name="report_template" colspan="4"
|
||||
domain="[('model','=',model)]"/>
|
||||
<field name="report_name" colspan="4" />
|
||||
</group>
|
||||
</page>
|
||||
<page string="Attach existing files">
|
||||
<field name="attachment_ids" colspan="4" nolabel="1" height="350"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
@ -103,9 +71,9 @@
|
|||
<field name="model">email.template</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Templates">
|
||||
<field name="model_id"/>
|
||||
<field name="mail_server_id" invisible="1"/>
|
||||
<field name="name"/>
|
||||
<field name="model_id"/>
|
||||
<field name="subject"/>
|
||||
<field name="email_from"/>
|
||||
<field name="email_to"/>
|
||||
|
|
|
@ -289,8 +289,8 @@ msgid "Add Signature"
|
|||
msgstr ""
|
||||
|
||||
#. module: email_template
|
||||
#: help:email.template,body_text:0
|
||||
#: help:email_template.preview,body_text:0
|
||||
#: help:email.template,body:0
|
||||
#: help:email_template.preview,body:0
|
||||
msgid "Plaintext version of the message (placeholders may be used here)"
|
||||
msgstr ""
|
||||
|
||||
|
@ -425,8 +425,8 @@ msgid "Cc"
|
|||
msgstr ""
|
||||
|
||||
#. module: email_template
|
||||
#: field:email.template,body_text:0
|
||||
#: field:email_template.preview,body_text:0
|
||||
#: field:email.template,body:0
|
||||
#: field:email_template.preview,body:0
|
||||
msgid "Text Contents"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_email_template,email.template,model_email_template,,1,0,0,0
|
||||
access_email_template,email.template,model_email_template,,1,1,1,0
|
||||
access_email_template_system,email.template system,model_email_template,base.group_system,1,1,1,1
|
||||
access_email_template_manager,email.template,model_email_template,,1,1,1,1
|
||||
access_email_template_preview_system,email.template.preview system,model_email_template_preview,base.group_system,1,1,1,1
|
||||
|
|
|
|
@ -1,8 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2012-TODAY OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -18,7 +18,10 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from . import test_mail
|
||||
|
||||
import project_mailgate
|
||||
checks = [
|
||||
test_mail,
|
||||
]
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,186 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2012-TODAY OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import base64
|
||||
from openerp.tests import common
|
||||
|
||||
class test_message_compose(common.TransactionCase):
|
||||
|
||||
def _mock_smtp_gateway(self, *args, **kwargs):
|
||||
return True
|
||||
|
||||
def _mock_build_email(self, *args, **kwargs):
|
||||
self._build_email_args = args
|
||||
self._build_email_kwargs = kwargs
|
||||
return self.build_email_real(*args, **kwargs)
|
||||
|
||||
def setUp(self):
|
||||
super(test_message_compose, self).setUp()
|
||||
self.mail_group = self.registry('mail.group')
|
||||
self.mail_mail = self.registry('mail.mail')
|
||||
self.mail_message = self.registry('mail.message')
|
||||
self.res_users = self.registry('res.users')
|
||||
self.res_partner = self.registry('res.partner')
|
||||
|
||||
# Install mock SMTP gateway
|
||||
self.build_email_real = self.registry('ir.mail_server').build_email
|
||||
self.registry('ir.mail_server').build_email = self._mock_build_email
|
||||
self.registry('ir.mail_server').send_email = self._mock_smtp_gateway
|
||||
|
||||
# create a 'pigs' and 'bird' groups that will be used through the various tests
|
||||
self.group_pigs_id = self.mail_group.create(self.cr, self.uid,
|
||||
{'name': 'Pigs', 'description': 'Fans of Pigs, unite !'})
|
||||
self.group_bird_id = self.mail_group.create(self.cr, self.uid,
|
||||
{'name': 'Bird', 'description': 'I am angry !'})
|
||||
|
||||
def test_00_message_compose_wizard(self):
|
||||
""" Tests designed for the mail.compose.message wizard updated by email_template. """
|
||||
cr, uid = self.cr, self.uid
|
||||
mail_compose = self.registry('mail.compose.message')
|
||||
self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a.a'})
|
||||
user_admin = self.res_users.browse(cr, uid, uid)
|
||||
p_a_id = user_admin.partner_id.id
|
||||
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
|
||||
group_bird = self.mail_group.browse(cr, uid, self.group_bird_id)
|
||||
|
||||
# Mail data
|
||||
_subject1 = 'Pigs'
|
||||
_subject2 = 'Bird'
|
||||
_body_html1 = 'Fans of Pigs, unite !\n<pre>Admin</pre>\n'
|
||||
_body_html2 = 'I am angry !\n<pre>Admin</pre>\n'
|
||||
_attachments = [
|
||||
{'name': 'First', 'datas_fname': 'first.txt', 'datas': base64.b64encode('My first attachment')},
|
||||
{'name': 'Second', 'datas_fname': 'second.txt', 'datas': base64.b64encode('My second attachment')}
|
||||
]
|
||||
_attachments_test = [('first.txt', 'My first attachment'), ('second.txt', 'My second attachment')]
|
||||
|
||||
# Create template on mail.group, with attachments
|
||||
group_model_id = self.registry('ir.model').search(cr, uid, [('model', '=', 'mail.group')])[0]
|
||||
email_template = self.registry('email.template')
|
||||
email_template_id = email_template.create(cr, uid, {'model_id': group_model_id,
|
||||
'name': 'Pigs Template', 'subject': '${object.name}',
|
||||
'body_html': '${object.description}', 'user_signature': True,
|
||||
'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])],
|
||||
'email_to': 'b@b.b c@c.c', 'email_cc': 'd@d.d'})
|
||||
|
||||
# ----------------------------------------
|
||||
# CASE1: comment and save as template
|
||||
# ----------------------------------------
|
||||
|
||||
# 1. Comment on pigs
|
||||
compose_id = mail_compose.create(cr, uid,
|
||||
{'subject': 'Forget me subject', 'body': '<p>Dummy body</p>'},
|
||||
{'default_composition_mode': 'comment', 'default_model': 'mail.group',
|
||||
'default_res_id': self.group_pigs_id,
|
||||
'default_template_id': email_template_id,
|
||||
'active_ids': [self.group_pigs_id, self.group_bird_id]})
|
||||
compose = mail_compose.browse(cr, uid, compose_id)
|
||||
|
||||
# 2. Save current composition form as a template
|
||||
mail_compose.save_as_template(cr, uid, [compose_id], context={'default_model': 'mail.group'})
|
||||
# Test: email_template subject, body_html, model
|
||||
last_template_id = email_template.search(cr, uid, [('model', '=', 'mail.group'), ('subject', '=', 'Forget me subject')], limit=1)[0]
|
||||
self.assertTrue(last_template_id, 'email_template not found for model mail.group, subject Forget me subject')
|
||||
last_template = email_template.browse(cr, uid, last_template_id)
|
||||
self.assertEqual(last_template.body_html, '<p>Dummy body</p>', 'email_template incorrect body_html')
|
||||
|
||||
# ----------------------------------------
|
||||
# CASE2: comment with template, save as template
|
||||
# ----------------------------------------
|
||||
|
||||
# 1. Comment on pigs
|
||||
compose_id = mail_compose.create(cr, uid,
|
||||
{'subject': 'Forget me subject', 'body': 'Dummy body'},
|
||||
{'default_composition_mode': 'comment', 'default_model': 'mail.group',
|
||||
'default_res_id': self.group_pigs_id,
|
||||
'default_template_id': email_template_id,
|
||||
'active_ids': [self.group_pigs_id, self.group_bird_id]})
|
||||
compose = mail_compose.browse(cr, uid, compose_id)
|
||||
|
||||
# 2. Perform 'toggle_template', to set use_template and use template_id
|
||||
mail_compose.toggle_template(cr, uid, [compose_id], {'default_composition_mode': 'comment', 'default_model': 'mail.group'})
|
||||
compose.refresh()
|
||||
message_pids = [partner.id for partner in compose.partner_ids]
|
||||
partner_ids = self.res_partner.search(cr, uid, [('email', 'in', ['b@b.b', 'c@c.c', 'd@d.d'])])
|
||||
# Test: mail.compose.message: subject, body, content_subtype, partner_ids
|
||||
self.assertEqual(compose.subject, _subject1, 'mail.compose.message subject incorrect')
|
||||
self.assertEqual(compose.body, _body_html1, 'mail.compose.message body incorrect')
|
||||
self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message content_subtype incorrect')
|
||||
self.assertEqual(set(message_pids), set(partner_ids), 'mail.compose.message partner_ids incorrect')
|
||||
# Test: mail.compose.message: attachments
|
||||
# Test: mail.message: attachments
|
||||
for attach in compose.attachment_ids:
|
||||
self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model incorrect')
|
||||
self.assertEqual(attach.res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect')
|
||||
self.assertIn((attach.name, base64.b64decode(attach.datas)), _attachments_test,
|
||||
'mail.message attachment name / data incorrect')
|
||||
|
||||
# 3. Perform 'toggle_template': template is not set anymore
|
||||
mail_compose.toggle_template(cr, uid, [compose_id], {'default_composition_mode': 'comment', 'default_model': 'mail.group'})
|
||||
compose.refresh()
|
||||
# Test: subject, body, partner_ids
|
||||
self.assertEqual(compose.subject, False, 'mail.compose.message subject incorrect')
|
||||
self.assertEqual(compose.body, '', 'mail.compose.message body incorrect')
|
||||
|
||||
# ----------------------------------------
|
||||
# CASE3: mass_mail with template
|
||||
# ----------------------------------------
|
||||
|
||||
# 1. Mass_mail on pigs and bird, with a default_partner_ids set to check he is correctly added
|
||||
compose_id = mail_compose.create(cr, uid,
|
||||
{'subject': 'Forget me subject', 'body': 'Dummy body'},
|
||||
{'default_composition_mode': 'mass_mail', 'default_model': 'mail.group',
|
||||
'default_res_id': self.group_pigs_id,
|
||||
'default_template_id': email_template_id,
|
||||
'default_partner_ids': [p_a_id],
|
||||
'active_ids': [self.group_pigs_id, self.group_bird_id]})
|
||||
compose = mail_compose.browse(cr, uid, compose_id)
|
||||
|
||||
# 2. Perform 'toggle_template', to set use_template and use template_id
|
||||
mail_compose.toggle_template(cr, uid, [compose_id], {'default_composition_mode': 'comment', 'default_model': 'mail.group'})
|
||||
compose.refresh()
|
||||
message_pids = [partner.id for partner in compose.partner_ids]
|
||||
partner_ids = [p_a_id]
|
||||
# Test: mail.compose.message: subject, body, content_subtype, partner_ids
|
||||
self.assertEqual(compose.subject, '${object.name}', 'mail.compose.message subject incorrect')
|
||||
self.assertEqual(compose.body, '${object.description}', 'mail.compose.message body incorrect')
|
||||
self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message content_subtype incorrect')
|
||||
self.assertEqual(set(message_pids), set(partner_ids), 'mail.compose.message partner_ids incorrect')
|
||||
|
||||
# 3. Post the comment, get created message
|
||||
mail_compose.send_mail(cr, uid, [compose_id], {'default_res_id': -1, 'active_ids': [self.group_pigs_id, self.group_bird_id]})
|
||||
group_pigs.refresh()
|
||||
group_bird.refresh()
|
||||
message_pigs = group_pigs.message_ids[0]
|
||||
message_bird = group_bird.message_ids[0]
|
||||
# Test: subject, body
|
||||
self.assertEqual(message_pigs.subject, _subject1, 'mail.message subject on Pigs incorrect')
|
||||
self.assertEqual(message_bird.subject, _subject2, 'mail.message subject on Bird incorrect')
|
||||
self.assertEqual(message_pigs.body, _body_html1, 'mail.message body on Pigs incorrect')
|
||||
self.assertEqual(message_bird.body, _body_html2, 'mail.message body on Bird incorrect')
|
||||
# Test: partner_ids: p_a_id (default) + 3 newly created partners
|
||||
message_pigs_pids = [partner.id for partner in message_pigs.partner_ids]
|
||||
message_bird_pids = [partner.id for partner in message_bird.partner_ids]
|
||||
partner_ids = self.res_partner.search(cr, uid, [('email', 'in', ['b@b.b', 'c@c.c', 'd@d.d'])]) + [p_a_id]
|
||||
self.assertEqual(len(message_pigs_pids), len(partner_ids), 'mail.message on pigs incorrect number of partner_ids')
|
||||
self.assertEqual(len(message_bird_pids), len(partner_ids), 'mail.message on bird partner_ids incorrect')
|
||||
self.assertEqual(set(message_pigs_pids), set(partner_ids), 'mail.message on pigs incorrect number of partner_ids')
|
||||
self.assertEqual(set(message_bird_pids), set(partner_ids), 'mail.message on bird partner_ids incorrect')
|
|
@ -26,7 +26,6 @@ class email_template_preview(osv.osv_memory):
|
|||
_inherit = "email.template"
|
||||
_name = "email_template.preview"
|
||||
_description = "Email Template Preview"
|
||||
_rec_name = "subject"
|
||||
|
||||
def _get_records(self, cr, uid, context=None):
|
||||
"""
|
||||
|
@ -66,30 +65,19 @@ class email_template_preview(osv.osv_memory):
|
|||
return result
|
||||
|
||||
_columns = {
|
||||
'res_id':fields.selection(_get_records, 'Sample Document'),
|
||||
'res_id': fields.selection(_get_records, 'Sample Document'),
|
||||
}
|
||||
|
||||
def on_change_res_id(self, cr, uid, ids, res_id, context=None):
|
||||
if not res_id:
|
||||
return {}
|
||||
if not res_id: return {}
|
||||
vals = {}
|
||||
email_template = self.pool.get('email.template')
|
||||
template_id = context and context.get('template_id')
|
||||
template = email_template.get_email_template(cr, uid, template_id=template_id, record_id=res_id, context=context)
|
||||
model = template.model
|
||||
vals['email_to'] = self.render_template(cr, uid, template.email_to, model, res_id, context)
|
||||
vals['email_cc'] = self.render_template(cr, uid, template.email_cc, model, res_id, context)
|
||||
vals['email_bcc'] = self.render_template(cr, uid, template.email_bcc, model, res_id, context)
|
||||
vals['reply_to'] = self.render_template(cr, uid, template.reply_to, model, res_id, context)
|
||||
vals['subject'] = self.render_template(cr, uid, template.subject, model, res_id, context)
|
||||
description = self.render_template(cr, uid, template.body_text, model, res_id, context) or ''
|
||||
if template.user_signature:
|
||||
signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
|
||||
description += '\n' + signature
|
||||
vals['body_text'] = description
|
||||
if template.body_html:
|
||||
vals['body_html'] = self.render_template(cr, uid, template.body_html, model, res_id, context) or ''
|
||||
vals['report_name'] = self.render_template(cr, uid, template.report_name, model, res_id, context)
|
||||
template = email_template.browse(cr, uid, template_id, context=context)
|
||||
vals['name'] = template.name
|
||||
mail_values = email_template.generate_email(cr, uid, template_id, res_id, context=context)
|
||||
for k in ('email_from','email_to','email_cc','reply_to','subject','body_html'):
|
||||
vals[k] = mail_values[k]
|
||||
return {'value': vals}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -6,27 +6,22 @@
|
|||
<field name="name">email_template.preview.form</field>
|
||||
<field name="model">email_template.preview</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Email Preview">
|
||||
<field name="model_id" invisible="1"/>
|
||||
<field name="res_id" on_change="on_change_res_id(res_id, context)"/>
|
||||
<group col="2" colspan="4">
|
||||
<field name="email_to" readonly="1"/>
|
||||
<field name="email_cc" readonly="1" attrs="{'invisible': [('email_cc','=',False)]}"/>
|
||||
<field name="email_bcc" readonly="1" attrs="{'invisible': [('email_bcc','=',False)]}"/>
|
||||
<field name="reply_to" readonly="1" attrs="{'invisible': [('reply_to','=',False)]}"/>
|
||||
<field name="subject" readonly="1"/>
|
||||
<form string="Email Preview" version="7.0">
|
||||
<div>
|
||||
<h2>Preview of <field name="name" readonly="1" class="oe_inline"/></h2>
|
||||
<field name="model_id" invisible="1"/>
|
||||
<h3>Using sample document
|
||||
<field name="res_id" on_change="on_change_res_id(res_id, context)" class="oe_inline"/>
|
||||
</h3>
|
||||
</div>
|
||||
<group>
|
||||
<field name="email_from" readonly="1"/>
|
||||
<field name="email_to" readonly="1"/>
|
||||
<field name="email_cc" readonly="1" attrs="{'invisible':[('email_cc','=',False)]}"/>
|
||||
<field name="reply_to" readonly="1" attrs="{'invisible':[('reply_to','=',False)]}"/>
|
||||
<field name="subject" readonly="1"/>
|
||||
<field name="body_html" widget="html" readonly="1"/>
|
||||
</group>
|
||||
<group col="4" colspan="4">
|
||||
<notebook>
|
||||
<page string="Body (Text)">
|
||||
<field name="body_text" nolabel="1" colspan="4" height="350" width="350" readonly="1"/>
|
||||
</page>
|
||||
<page string="Body (Rich/HTML)" attrs="{'invisible': [('body_html','=', False)]}">
|
||||
<field name="body_html" nolabel="1" colspan="4" height="350" width="350" readonly="1"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</group>
|
||||
<field name="report_name" colspan="4" readonly="1"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
|
|
@ -19,138 +19,155 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import base64
|
||||
import tools
|
||||
|
||||
from osv import osv
|
||||
from osv import fields
|
||||
from tools.translate import _
|
||||
import tools
|
||||
|
||||
|
||||
class mail_compose_message(osv.osv_memory):
|
||||
_inherit = 'mail.compose.message'
|
||||
|
||||
def _get_templates(self, cr, uid, context=None):
|
||||
"""
|
||||
Return Email Template of particular Model.
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
record_ids = []
|
||||
email_template= self.pool.get('email.template')
|
||||
model = False
|
||||
if context.get('message_id'):
|
||||
mail_message = self.pool.get('mail.message')
|
||||
message_data = mail_message.browse(cr, uid, int(context.get('message_id')), context)
|
||||
model = message_data.model
|
||||
elif context.get('mail.compose.target.model') or context.get('active_model'):
|
||||
model = context.get('mail.compose.target.model', context.get('active_model'))
|
||||
email_template_obj = self.pool.get('email.template')
|
||||
message_id = context.get('default_parent_id', context.get('message_id', context.get('active_id')))
|
||||
|
||||
if context.get('default_composition_mode') == 'reply' and message_id:
|
||||
message_data = self.pool.get('mail.message').browse(cr, uid, message_id, context=context)
|
||||
if message_data:
|
||||
model = message_data.model
|
||||
else:
|
||||
model = context.get('default_model', context.get('active_model'))
|
||||
|
||||
if model:
|
||||
record_ids = email_template.search(cr, uid, [('model', '=', model)])
|
||||
return email_template.name_get(cr, uid, record_ids, context) + [(False,'')]
|
||||
record_ids = email_template_obj.search(cr, uid, [('model', '=', model)], context=context)
|
||||
return email_template_obj.name_get(cr, uid, record_ids, context) + [(False, '')]
|
||||
return []
|
||||
|
||||
def default_get(self, cr, uid, fields, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
result = super(mail_compose_message, self).default_get(cr, uid, fields, context=context)
|
||||
result['template_id'] = context.get('default_template_id', context.get('mail.compose.template_id', False))
|
||||
# force html when using templates
|
||||
if result.get('use_template'):
|
||||
result['content_subtype'] = 'html'
|
||||
return result
|
||||
|
||||
_columns = {
|
||||
'use_template': fields.boolean('Use Template'),
|
||||
'template_id': fields.selection(_get_templates, 'Template',
|
||||
size=-1 # means we want an int db column
|
||||
),
|
||||
# incredible hack of the day: size=-1 means we want an int db column instead of an str one
|
||||
'template_id': fields.selection(_get_templates, 'Template', size=-1),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'template_id' : lambda self, cr, uid, context={} : context.get('mail.compose.template_id', False)
|
||||
}
|
||||
|
||||
def on_change_template(self, cr, uid, ids, use_template, template_id, email_from=None, email_to=None, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
values = {}
|
||||
if template_id:
|
||||
res_id = context.get('active_id', False)
|
||||
if context.get('mail.compose.message.mode') == 'mass_mail':
|
||||
# use the original template values - to be rendered when actually sent
|
||||
# by super.send_mail()
|
||||
values = self.pool.get('email.template').read(cr, uid, template_id, self.fields_get_keys(cr, uid), context)
|
||||
else:
|
||||
# render the mail as one-shot
|
||||
values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context)
|
||||
# get partner_ids back
|
||||
values['dest_partner_ids'] = values['partner_ids']
|
||||
# retrofit generated attachments in the expected field format
|
||||
if values['attachments']:
|
||||
attachment = values.pop('attachments')
|
||||
attachment_obj = self.pool.get('ir.attachment')
|
||||
att_ids = []
|
||||
for fname, fcontent in attachment.iteritems():
|
||||
data_attach = {
|
||||
'name': fname,
|
||||
'datas': fcontent,
|
||||
'datas_fname': fname,
|
||||
'description': fname,
|
||||
'res_model' : self._name,
|
||||
'res_id' : ids[0] if ids else False
|
||||
}
|
||||
att_ids.append(attachment_obj.create(cr, uid, data_attach))
|
||||
values['attachment_ids'] = att_ids
|
||||
def onchange_template_id(self, cr, uid, ids, use_template, template_id, composition_mode, model, res_id, context=None):
|
||||
""" - use_template not set: return default_get
|
||||
- use_template set in mass_mailing: we cannot render, so return the template values
|
||||
- use_template set: return rendered values """
|
||||
if use_template and template_id and composition_mode == 'mass_mail':
|
||||
values = self.pool.get('email.template').read(cr, uid, template_id, ['subject', 'body_html'], context)
|
||||
values.pop('id')
|
||||
elif use_template and template_id:
|
||||
values = self.generate_email_for_composer(cr, uid, template_id, res_id, context=context)
|
||||
# transform attachments into attachment_ids
|
||||
values['attachment_ids'] = []
|
||||
ir_attach_obj = self.pool.get('ir.attachment')
|
||||
for attach_fname, attach_datas in values.pop('attachments', []):
|
||||
data_attach = {
|
||||
'name': attach_fname,
|
||||
'datas': attach_datas,
|
||||
'datas_fname': attach_fname,
|
||||
'res_model': model,
|
||||
'res_id': res_id,
|
||||
}
|
||||
values['attachment_ids'].append(ir_attach_obj.create(cr, uid, data_attach, context=context))
|
||||
else:
|
||||
# restore defaults
|
||||
values = self.default_get(cr, uid, self.fields_get_keys(cr, uid), context)
|
||||
values.update(use_template=use_template, template_id=template_id)
|
||||
values = self.default_get(cr, uid, ['body', 'body_html', 'subject', 'partner_ids', 'attachment_ids'], context=context)
|
||||
|
||||
if values.get('body_html'):
|
||||
values['body'] = values.pop('body_html')
|
||||
values.update(use_template=use_template, template_id=template_id)
|
||||
return {'value': values}
|
||||
|
||||
def template_toggle(self, cr, uid, ids, context=None):
|
||||
def toggle_template(self, cr, uid, ids, context=None):
|
||||
""" hit toggle template mode button: calls onchange_use_template to
|
||||
emulate an on_change, then writes the values to update the form. """
|
||||
for record in self.browse(cr, uid, ids, context=context):
|
||||
values = {}
|
||||
use_template = record.use_template
|
||||
# simulate an on_change on use_template
|
||||
values.update(self.onchange_use_template(cr, uid, ids, not use_template, context=context)['value'])
|
||||
record.write(values)
|
||||
return False
|
||||
onchange_res = self.onchange_use_template(cr, uid, ids, not record.use_template,
|
||||
record.template_id, record.composition_mode, record.model, record.res_id, context=context).get('value', {})
|
||||
# update partner_ids and attachment_ids
|
||||
onchange_res['partner_ids'] = [(4, partner_id) for partner_id in onchange_res.pop('partner_ids', [])]
|
||||
onchange_res['attachment_ids'] = [(4, attachment_id) for attachment_id in onchange_res.pop('attachment_ids', [])]
|
||||
record.write(onchange_res)
|
||||
return True
|
||||
|
||||
def onchange_use_template(self, cr, uid, ids, use_template, context=None):
|
||||
values = {'use_template': use_template}
|
||||
for record in self.browse(cr, uid, ids, context=context):
|
||||
if not use_template:
|
||||
# equivalent to choosing an empty template
|
||||
onchange_template_values = self.on_change_template(cr, uid, record.id, use_template,
|
||||
False, email_from=record.email_from, email_to=record.email_to, context=context)
|
||||
values.update(onchange_template_values['value'])
|
||||
return {'value': values}
|
||||
def onchange_use_template(self, cr, uid, ids, use_template, template_id, composition_mode, model, res_id, context=None):
|
||||
""" onchange_use_template (values: True or False). If use_template is
|
||||
False, we do as an onchange with template_id False for values """
|
||||
values = self.onchange_template_id(cr, uid, ids, use_template,
|
||||
template_id, composition_mode, model, res_id, context=context)
|
||||
# force html when using templates
|
||||
if use_template:
|
||||
values['value']['content_subtype'] = 'html'
|
||||
return values
|
||||
|
||||
def save_as_template(self, cr, uid, ids, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
""" hit save as template button: current form value will be a new
|
||||
template attached to the current document. """
|
||||
email_template = self.pool.get('email.template')
|
||||
model_pool = self.pool.get('ir.model')
|
||||
ir_model_pool = self.pool.get('ir.model')
|
||||
for record in self.browse(cr, uid, ids, context=context):
|
||||
model = record.model or context.get('active_model')
|
||||
model_ids = model_pool.search(cr, uid, [('model', '=', model)])
|
||||
model_ids = ir_model_pool.search(cr, uid, [('model', '=', record.model)], context=context)
|
||||
model_id = model_ids and model_ids[0] or False
|
||||
model_name = ''
|
||||
if model_id:
|
||||
model_name = model_pool.browse(cr, uid, model_id, context=context).name
|
||||
model_name = ir_model_pool.browse(cr, uid, model_id, context=context).name
|
||||
template_name = "%s: %s" % (model_name, tools.ustr(record.subject))
|
||||
values = {
|
||||
'name': template_name,
|
||||
'email_from': record.email_from or False,
|
||||
'subject': record.subject or False,
|
||||
'body_text': record.body_text or False,
|
||||
'email_to': record.email_to or False,
|
||||
'email_cc': record.email_cc or False,
|
||||
'email_bcc': record.email_bcc or False,
|
||||
'reply_to': record.reply_to or False,
|
||||
'body_html': record.body or False,
|
||||
'model_id': model_id or False,
|
||||
'attachment_ids': [(6, 0, [att.id for att in record.attachment_ids])]
|
||||
}
|
||||
template_id = email_template.create(cr, uid, values, context=context)
|
||||
record.write({'template_id': template_id,
|
||||
'use_template': True})
|
||||
record.write({'template_id': template_id, 'use_template': True})
|
||||
return True
|
||||
|
||||
# _reopen same wizard screen with new template preselected
|
||||
return False
|
||||
#------------------------------------------------------
|
||||
# Wizard validation and send
|
||||
#------------------------------------------------------
|
||||
|
||||
def generate_email_for_composer(self, cr, uid, template_id, res_id, context=None):
|
||||
""" Call email_template.generate_email(), get fields relevant for
|
||||
mail.compose.message, transform email_cc and email_to into partner_ids """
|
||||
template_values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context)
|
||||
# filter template values
|
||||
fields = ['body', 'body_html', 'subject', 'email_to', 'email_cc', 'attachments']
|
||||
values = dict((field, template_values[field]) for field in fields if template_values.get(field))
|
||||
values['body'] = values.pop('body_html', '')
|
||||
# transform email_to, email_cc into partner_ids
|
||||
values['partner_ids'] = []
|
||||
mails = tools.email_split(values.pop('email_to', '') + ' ' + values.pop('email_cc', ''))
|
||||
for mail in mails:
|
||||
partner_id = self.pool.get('res.partner').find_or_create(cr, uid, mail, context=context)
|
||||
values['partner_ids'].append(partner_id)
|
||||
return values
|
||||
|
||||
def render_message(self, cr, uid, wizard, res_id, context=None):
|
||||
""" Generate an email from the template for given (model, res_id) pair.
|
||||
This method is meant to be inherited by email_template that will
|
||||
produce a more complete dictionary, with email_to, ...
|
||||
"""
|
||||
# generate the composer email
|
||||
values = self.generate_email_for_composer(cr, uid, wizard.template_id, res_id, context=context)
|
||||
# get values to return
|
||||
email_dict = super(mail_compose_message, self).render_message(cr, uid, wizard, res_id, context)
|
||||
email_dict.update(values)
|
||||
return email_dict
|
||||
|
||||
# override the basic implementation
|
||||
def render_template(self, cr, uid, template, model, res_id, context=None):
|
||||
return self.pool.get('email.template').render_template(cr, uid, template, model, res_id, context=context)
|
||||
|
||||
|
|
|
@ -10,15 +10,16 @@
|
|||
<data>
|
||||
<xpath expr="//form/footer/button" position="before">
|
||||
<field name="use_template" invisible="1"/>
|
||||
<button icon="gtk-paste" type="object" name="template_toggle"
|
||||
<button icon="gtk-paste" type="object" name="toggle_template"
|
||||
string="" help="Use a message template" />
|
||||
<button icon="gtk-save" type="object" name="save_as_template"
|
||||
string="" help="Save as a new template"/>
|
||||
</xpath>
|
||||
<xpath expr="//form/notebook" position="after">
|
||||
<group attrs="{'invisible':[('use_template','=',False)]}" colspan="4" col="4">
|
||||
<field name="use_template" invisible="1"/>
|
||||
<field name="template_id" colspan="3"
|
||||
on_change="on_change_template(use_template, template_id, email_from, email_to, context)"/>
|
||||
on_change="onchange_template_id(use_template, template_id, composition_mode, model, res_id, context)"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</data>
|
||||
|
@ -31,20 +32,22 @@
|
|||
<field name="inherit_id" ref="mail.email_compose_message_wizard_form_chatter"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//field[@name='dest_partner_ids']" position="after">
|
||||
<xpath expr="//field[@name='partner_ids']" position="after">
|
||||
<field name="use_template" colspan="2" nolabel="1" invisible="1"
|
||||
on_change="onchange_use_template(use_template, context)"/>
|
||||
on_change="onchange_use_template(use_template, template_id, composition_mode, model, res_id, context)"/>
|
||||
<field name="template_id" colspan="2" nolabel="1"
|
||||
attrs="{'invisible':[('use_template','=',False)]}"
|
||||
on_change="on_change_template(use_template, template_id, False, False, context)"/>
|
||||
on_change="onchange_template_id(use_template, template_id, composition_mode, model, res_id, context)"/>
|
||||
</xpath>
|
||||
<xpath expr="//a[@class='oe_mail_compose_message_checklist']" position="before">
|
||||
<xpath expr="//button[@class='oe_mail_compose_message_attachment']" position="before">
|
||||
<button icon="/email_template/static/src/img/email_template.png"
|
||||
type="object" name="template_toggle" string=""
|
||||
help="Use a message template"/>
|
||||
type="object" name="toggle_template" string=""
|
||||
help="Use a message template"
|
||||
attrs="{'invisible':[('content_subtype','!=','html')]}"/>
|
||||
<button icon="/email_template/static/src/img/email_template_save.png"
|
||||
type="object" name="save_as_template" string=""
|
||||
help="Save as a new template"/>
|
||||
help="Save as a new template"
|
||||
attrs="{'invisible':[('content_subtype','!=','html')]}"/>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
|
|
|
@ -7,15 +7,12 @@
|
|||
<field name="email_from" >${object.user_id.email or object.company_id.email or 'noreply@' + object.company_id.name + '.com'}</field>
|
||||
<field name="email_to" >${object.email}</field>
|
||||
<field name="subject">Your registration at ${object.event_id.name}</field>
|
||||
<field name="body_text">
|
||||
hello ${object.name},
|
||||
|
||||
The event ${object.event_id.name} that you registered for is confirmed and will be held from ${object.event_id.date_begin} to ${object.event_id.date_end}. For any further information please contact our event department.
|
||||
|
||||
we thank you for your participation
|
||||
|
||||
best regards
|
||||
</field>
|
||||
<field name="body_html"><![CDATA[
|
||||
<p>Hello ${object.name},</p>
|
||||
<p>The event ${object.event_id.name} that you registered for is confirmed and will be held from ${object.event_id.date_begin} to ${object.event_id.date_end}.
|
||||
For any further information please contact our event department.</p>
|
||||
<p>Thank you for your participation!</p>
|
||||
<p>Best regards</p>]]></field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
|
@ -27,15 +24,12 @@
|
|||
<field name="email_from" >${object.user_id.email or object.company_id.email or 'noreply@' + object.company_id.name + '.com'}</field>
|
||||
<field name="email_to" >${object.email}</field>
|
||||
<field name="subject">Your registration at ${object.event_id.name}</field>
|
||||
<field name="body_text">
|
||||
hello ${object.name},
|
||||
|
||||
We confirm you that your registration to the event ${object.event_id.name} has been recorded. You will automatically receive an email providing you more practical information (such as the schedule, the plan...) as soon as the event will be confirmed to be held.
|
||||
|
||||
we thank you for your participation
|
||||
|
||||
best regards
|
||||
</field>
|
||||
<field name="body_html"><![CDATA[
|
||||
<p>Hello ${object.name},</p>
|
||||
<p>We confirm that your registration to the event ${object.event_id.name} has been recorded.
|
||||
You will automatically receive an email providing you more practical information (such as the schedule, the agenda...) as soon as the event is confirmed.</p>
|
||||
<p>Thank you for your participation!</p>
|
||||
<p>Best regards</p>]]></field>
|
||||
</record>
|
||||
|
||||
<!-- Default Values -->
|
||||
|
|
|
@ -19,10 +19,8 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import time
|
||||
from osv import fields, osv
|
||||
from tools.translate import _
|
||||
import decimal_precision as dp
|
||||
from openerp import SUPERUSER_ID
|
||||
|
||||
class event_type(osv.osv):
|
||||
|
@ -49,11 +47,11 @@ class event_event(osv.osv):
|
|||
_name = 'event.event'
|
||||
_description = __doc__
|
||||
_order = 'date_begin'
|
||||
_inherit = ['ir.needaction_mixin','mail.thread']
|
||||
_inherit = ['mail.thread','ir.needaction_mixin']
|
||||
|
||||
def name_get(self, cr, uid, ids, context=None):
|
||||
if not ids:
|
||||
return []
|
||||
return []
|
||||
res = []
|
||||
for record in self.browse(cr, uid, ids, context=context):
|
||||
date = record.date_begin.split(" ")[0]
|
||||
|
@ -99,7 +97,6 @@ class event_event(osv.osv):
|
|||
return self.write(cr, uid, ids, {'state': 'done'}, context=context)
|
||||
|
||||
def check_registration_limits(self, cr, uid, ids, context=None):
|
||||
register_pool = self.pool.get('event.registration')
|
||||
for self.event in self.browse(cr, uid, ids, context=context):
|
||||
total_confirmed = self.event.register_current
|
||||
if total_confirmed < self.event.register_min or total_confirmed > self.event.register_max and self.event.register_max!=0:
|
||||
|
@ -109,7 +106,7 @@ class event_event(osv.osv):
|
|||
for event in self.browse(cr, uid, ids, context=context):
|
||||
available_seats = event.register_avail
|
||||
if available_seats and no_of_registration > available_seats:
|
||||
raise osv.except_osv(_('Warning!'),_("Only %d Seats are Available!") % (available_seats))
|
||||
raise osv.except_osv(_('Warning!'),_("Only %d Seats are Available!") % (available_seats))
|
||||
elif available_seats == 0:
|
||||
raise osv.except_osv(_('Warning!'),_("No Tickets Available!"))
|
||||
|
||||
|
@ -139,7 +136,6 @@ class event_event(osv.osv):
|
|||
@param context: A standard dictionary for contextual values
|
||||
@return: Dictionary of function fields value.
|
||||
"""
|
||||
register_pool = self.pool.get('event.registration')
|
||||
res = {}
|
||||
for event in self.browse(cr, uid, ids, context=context):
|
||||
res[event.id] = {}
|
||||
|
@ -287,27 +283,27 @@ class event_event(osv.osv):
|
|||
|
||||
def create_send_note(self, cr, uid, ids, context=None):
|
||||
message = _("Event has been <b>created</b>.")
|
||||
self.message_append_note(cr, uid, ids, body=message, context=context)
|
||||
self.message_post(cr, uid, ids, body=message, context=context)
|
||||
return True
|
||||
|
||||
def button_cancel_send_note(self, cr, uid, ids, context=None):
|
||||
message = _("Event has been <b>cancelled</b>.")
|
||||
self.message_append_note(cr, uid, ids, body=message, context=context)
|
||||
self.message_post(cr, uid, ids, body=message, context=context)
|
||||
return True
|
||||
|
||||
def button_draft_send_note(self, cr, uid, ids, context=None):
|
||||
message = _("Event has been set to <b>draft</b>.")
|
||||
self.message_append_note(cr, uid, ids, body=message, context=context)
|
||||
self.message_post(cr, uid, ids, body=message, context=context)
|
||||
return True
|
||||
|
||||
def button_done_send_note(self, cr, uid, ids, context=None):
|
||||
message = _("Event has been <b>done</b>.")
|
||||
self.message_append_note(cr, uid, ids, body=message, context=context)
|
||||
self.message_post(cr, uid, ids, body=message, context=context)
|
||||
return True
|
||||
|
||||
def button_confirm_send_note(self, cr, uid, ids, context=None):
|
||||
message = _("Event has been <b>confirmed</b>.")
|
||||
self.message_append_note(cr, uid, ids, body=message, context=context)
|
||||
self.message_post(cr, uid, ids, body=message, context=context)
|
||||
return True
|
||||
|
||||
event_event()
|
||||
|
@ -353,7 +349,7 @@ class event_registration(osv.osv):
|
|||
return self.write(cr, uid, ids, {'state': 'draft'}, context=context)
|
||||
|
||||
def confirm_registration(self, cr, uid, ids, context=None):
|
||||
self.message_append(cr, uid, ids,_('State set to open'),body_text= _('Open'))
|
||||
self.message_post(cr, uid, ids, body=_('State set to open'), context=context)
|
||||
return self.write(cr, uid, ids, {'state': 'open'}, context=context)
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
|
@ -383,13 +379,13 @@ class event_registration(osv.osv):
|
|||
if today >= registration.event_id.date_begin:
|
||||
values = {'state': 'done', 'date_closed': today}
|
||||
self.write(cr, uid, ids, values)
|
||||
self.message_append(cr, uid, ids, _('State set to Done'), body_text=_('Done'))
|
||||
self.message_post(cr, uid, ids, body=_('State set to Done'), context=context)
|
||||
else:
|
||||
raise osv.except_osv(_('Error!'),_("You must wait for the starting day of the event to do this action.") )
|
||||
return True
|
||||
|
||||
def button_reg_cancel(self, cr, uid, ids, context=None, *args):
|
||||
self.message_append(cr, uid, ids,_('State set to Cancel'),body_text= _('Cancel'))
|
||||
self.message_post(cr, uid, ids, body=_('State set to Cancel'), context=context)
|
||||
return self.write(cr, uid, ids, {'state': 'cancel'})
|
||||
|
||||
def mail_user(self, cr, uid, ids, context=None):
|
||||
|
@ -459,12 +455,12 @@ class event_registration(osv.osv):
|
|||
|
||||
def create_send_note(self, cr, uid, ids, context=None):
|
||||
message = _("Registration has been <b>created</b>.")
|
||||
self.message_append_note(cr, uid, ids, body=message, context=context)
|
||||
self.message_post(cr, uid, ids, body=message, context=context)
|
||||
return True
|
||||
|
||||
def do_draft_send_note(self, cr, uid, ids, context=None):
|
||||
message = _("Registration has been set as <b>draft</b>.")
|
||||
self.message_append_note(cr, uid, ids, body=message, context=context)
|
||||
self.message_post(cr, uid, ids, body=message, context=context)
|
||||
return True
|
||||
|
||||
event_registration()
|
||||
|
|
|
@ -14,11 +14,13 @@
|
|||
</record>
|
||||
|
||||
<!-- notify all employees of module installation -->
|
||||
<function model="mail.group" name="message_append_note">
|
||||
<!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
|
||||
<value eval="[ref('mail.group_all_employees')]"/>
|
||||
<value>Module Events Organisation has been installed</value>
|
||||
<value>From the top menu Events, you can organize events, manage registrations, automate communication around your event and sell events through your quotations.</value>
|
||||
</function>
|
||||
<record model="mail.message" id="module_install_notification">
|
||||
<field name="model">mail.group</field>
|
||||
<field name="res_id" ref="mail.group_all_employees"/>
|
||||
<field name="type">notification</field>
|
||||
<field name="subject">Events Organisation application installed!</field>
|
||||
<field name="body">From the top Events menu, you can organize events, manage registrations, automate communication around your event and sell events through your quotations.</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -208,7 +208,7 @@
|
|||
<field name="name">event.event.tree</field>
|
||||
<field name="model">event.event</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Events" fonts="bold:needaction_pending==True" colors="red:(register_min and register_min>register_current) or (register_max and register_max<register_current);grey:state=='cancel'">
|
||||
<tree string="Events" fonts="bold:message_unread==True" colors="red:(register_min and register_min>register_current) or (register_max and register_max<register_current);grey:state=='cancel'">
|
||||
<field name="name" string="Name"/>
|
||||
<field name="type"/>
|
||||
<field name="date_begin"/>
|
||||
|
@ -219,7 +219,7 @@
|
|||
<field name="main_speaker_id" groups="base.extended"/>
|
||||
<field name="user_id"/>
|
||||
<field name="state"/>
|
||||
<field name="needaction_pending" invisible="1"/>
|
||||
<field name="message_unread" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
@ -326,7 +326,7 @@
|
|||
<field name="arch" type="xml">
|
||||
<search string="Events">
|
||||
<field name="name" string="Events"/>
|
||||
<filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="needaction_pending" domain="[('needaction_pending','=',True)]"/>
|
||||
<filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
|
||||
<separator/>
|
||||
<filter icon="terp-check" string="Unconfirmed" name="draft" domain="[('state','=','draft')]" help="Events in New state"/>
|
||||
<filter icon="terp-camera_test" string="Confirmed" domain="[('state','=','confirm')]" help="Confirmed events"/>
|
||||
|
@ -421,7 +421,7 @@
|
|||
<field name="name">event.registration.tree</field>
|
||||
<field name="model">event.registration</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Registration" fonts="bold:needaction_pending==True">
|
||||
<tree string="Registration" fonts="bold:message_unread==True">
|
||||
<field name="create_date"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="name"/>
|
||||
|
@ -431,7 +431,7 @@
|
|||
<field name="user_id"/>
|
||||
<field name="origin"/>
|
||||
<field name="state"/>
|
||||
<field name="needaction_pending" invisible="1"/>
|
||||
<field name="message_unread" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
@ -516,7 +516,7 @@
|
|||
<field name="arch" type="xml">
|
||||
<search string="Event Registration">
|
||||
<field name="name" string="Participant" filter_domain="['|','|','|',('name','ilike',self),('partner_id','ilike',self),('email','ilike',self),('origin','ilike',self)]"/>
|
||||
<filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="needaction_pending" domain="[('needaction_pending','=',True)]"/>
|
||||
<filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
|
||||
<separator/>
|
||||
<filter icon="terp-check" string="New" name="draft" domain="[('state','=','draft')]" help="Registrations in unconfirmed state"/>
|
||||
<filter icon="terp-camera_test" string="Confirmed" domain="[('state','=','open')]" help="Confirmed registrations"/>
|
||||
|
|
|
@ -88,5 +88,5 @@ class sale_order_line(osv.osv):
|
|||
}
|
||||
registration_id = registration_obj.create(cr, uid, dic, context=context)
|
||||
message = _("The registration %s has been created from the Sale Order %s.") % (registration_id, order_line.order_id.name)
|
||||
registration_obj.message_append_note(cr, uid, [registration_id], body=message, context=context)
|
||||
registration_obj.message_post(cr, uid, [registration_id], body=message, context=context)
|
||||
return super(sale_order_line, self).button_confirm(cr, uid, ids, context=context)
|
||||
|
|
|
@ -77,7 +77,7 @@ class fetchmail_server(osv.osv):
|
|||
"emails to the existing conversations (documents)."),
|
||||
'priority': fields.integer('Server Priority', readonly=True, states={'draft':[('readonly', False)]}, help="Defines the order of processing, "
|
||||
"lower values mean higher priority"),
|
||||
'message_ids': fields.one2many('mail.message', 'fetchmail_server_id', 'Messages', readonly=True),
|
||||
'message_ids': fields.one2many('mail.mail', 'fetchmail_server_id', 'Messages', readonly=True),
|
||||
'configuration' : fields.text('Configuration'),
|
||||
'script' : fields.char('Script', readonly=True, size=64),
|
||||
}
|
||||
|
@ -232,8 +232,8 @@ openerp_mailgate.py -u %(uid)d -p PASSWORD -o %(model)s -d %(dbname)s --host=HOS
|
|||
server.write({'date': time.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)})
|
||||
return True
|
||||
|
||||
class mail_message(osv.osv):
|
||||
_inherit = "mail.message"
|
||||
class mail_mail(osv.osv):
|
||||
_inherit = "mail.mail"
|
||||
_columns = {
|
||||
'fetchmail_server_id': fields.many2one('fetchmail.server', "Inbound Mail Server",
|
||||
readonly=True,
|
||||
|
@ -247,7 +247,7 @@ class mail_message(osv.osv):
|
|||
fetchmail_server_id = context.get('fetchmail_server_id')
|
||||
if fetchmail_server_id:
|
||||
values['fetchmail_server_id'] = fetchmail_server_id
|
||||
res = super(mail_message,self).create(cr, uid, values, context=context)
|
||||
res = super(mail_mail,self).create(cr, uid, values, context=context)
|
||||
return res
|
||||
|
||||
def write(self, cr, uid, ids, values, context=None):
|
||||
|
@ -256,7 +256,7 @@ class mail_message(osv.osv):
|
|||
fetchmail_server_id = context.get('fetchmail_server_id')
|
||||
if fetchmail_server_id:
|
||||
values['fetchmail_server_id'] = server_id
|
||||
res = super(mail_message,self).write(cr, uid, ids, values, context=context)
|
||||
res = super(mail_mail,self).write(cr, uid, ids, values, context=context)
|
||||
return res
|
||||
|
||||
|
||||
|
|
|
@ -104,27 +104,16 @@
|
|||
/>
|
||||
|
||||
<record model="ir.ui.view" id="email_message_tree_view">
|
||||
<field name="name">mail.message.list.fetchmail</field>
|
||||
<field name="model">mail.message</field>
|
||||
<field name="inherit_id" ref="mail.view_email_message_tree"/>
|
||||
<field name="name">mail.mail.form.fetchmail</field>
|
||||
<field name="model">mail.mail</field>
|
||||
<field name="inherit_id" ref="mail.view_mail_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="user_id" position="after">
|
||||
<field name="references" position="after">
|
||||
<field name="fetchmail_server_id"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="email_message_search_view">
|
||||
<field name="name">mail.message.inherit.search</field>
|
||||
<field name="model">mail.message</field>
|
||||
<field name="inherit_id" ref="mail.view_email_message_search"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="/search/group/filter[@string='Thread']" position="before">
|
||||
<filter string="Mail Server" icon="terp-accessories-archiver" domain="[]" context="{'group_by':'fetchmail_server_id'}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<act_window
|
||||
context="{'search_default_server_id': active_id, 'default_fetchmail_server_id': active_id}"
|
||||
id="act_server_history" name="Messages" domain="[('email_from', '!=', False), ('fetchmail_server_id', '=', active_id)]"
|
||||
|
|
|
@ -212,7 +212,7 @@ class hr_employee(osv.osv):
|
|||
try:
|
||||
(model, mail_group_id) = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', 'group_all_employees')
|
||||
employee = self.browse(cr, uid, employee_id, context=context)
|
||||
self.pool.get('mail.group').message_append_note(cr, uid, [mail_group_id], body='Welcome to %s! Please help him make its first steps in OpenERP!' % (employee.name), context=context)
|
||||
self.pool.get('mail.group').message_post(cr, uid, [mail_group_id], body='Welcome to %s! Please help them take the first steps with OpenERP!' % (employee.name), context=context)
|
||||
except:
|
||||
pass # group deleted: do not push a message
|
||||
return employee_id
|
||||
|
|
|
@ -2,14 +2,16 @@
|
|||
<openerp>
|
||||
<data noupdate="1">
|
||||
<!-- notify all employees of module installation -->
|
||||
<function model="mail.group" name="message_append_note">
|
||||
<!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
|
||||
<value eval="[ref('mail.group_all_employees')]"/>
|
||||
<value>Module Employee Directory has been installed</value>
|
||||
<value>Manage your human resources in OpenERP: employees and hierarchy, HR departments and jobs.
|
||||
<record model="mail.message" id="module_install_notification">
|
||||
<field name="model">mail.group</field>
|
||||
<field name="res_id" ref="mail.group_all_employees"/>
|
||||
<field name="type">notification</field>
|
||||
<field name="subject">Employee Directory application installed!</field>
|
||||
<field name="body">Manage your human resources with OpenERP: employees and their hierarchy, HR departments and job positions.
|
||||
|
||||
More HR features are available from the following modules: Recruitment Process (manage job positions and recruitment), Timesheets Validation (record timesheets and attendance), Leaves Management (keep track of employee leaves), Expenses Management (manage employee expenses), Employee Appraisals (organize employee surveys, where employees evaluate their subordinates or their manager.)</value>
|
||||
</function>
|
||||
More HR features are available via extra applications: Recruitment Process (manage job positions and recruitment), Timesheet Validation (record timesheets and attendance),
|
||||
Leave Management (keep track of employee leaves), Expense Management (manage employee expenses), Employee Appraisals (organize employee surveys, where employees evaluate their subordinates or their manager).</field>
|
||||
</record>
|
||||
|
||||
<record id="employee" model="hr.employee">
|
||||
<field name="name">Administrator</field>
|
||||
|
|
|
@ -38,11 +38,10 @@ created and it can be defined which level of employee hierarchy fills what and
|
|||
final review and evaluation is done by the manager. Every evaluation filled by
|
||||
the employees can be viewed in the form of pdf file.
|
||||
""",
|
||||
'demo': ['hr_evaluation_demo.xml'],
|
||||
'data': [
|
||||
"demo": ["hr_evaluation_demo.xml"],
|
||||
"data": [
|
||||
'security/ir.model.access.csv',
|
||||
'security/hr_evaluation_security.xml',
|
||||
# 'wizard/hr_evaluation_mail_view.xml',
|
||||
'hr_evaluation_view.xml',
|
||||
'report/hr_evaluation_report_view.xml',
|
||||
'board_hr_evaluation_view.xml',
|
||||
|
|
|
@ -228,9 +228,13 @@ class hr_evaluation(osv.osv):
|
|||
body = phase.mail_body % {'employee_name': child.name, 'user_signature': child.user_id.signature,
|
||||
'eval_name': phase.survey_id.title, 'date': time.strftime('%Y-%m-%d'), 'time': time }
|
||||
sub = phase.email_subject
|
||||
dest = [child.work_email]
|
||||
if dest:
|
||||
mail_message.schedule_with_attach(cr, uid, evaluation.employee_id.work_email, dest, sub, body, context=context)
|
||||
if child.work_email:
|
||||
vals = {'state': 'outgoing',
|
||||
'subject': sub,
|
||||
'body_html': '<pre>%s</pre>' % body,
|
||||
'email_to': child.work_email,
|
||||
'email_from': evaluation.employee_id.work_email}
|
||||
self.pool.get('mail.mail').create(cr, uid, vals, context=context)
|
||||
|
||||
self.write(cr, uid, ids, {'state':'wait'}, context=context)
|
||||
return True
|
||||
|
|
|
@ -14,12 +14,14 @@
|
|||
<field name="sequence">100</field>
|
||||
</record>
|
||||
<!-- notify all employees of module installation -->
|
||||
<function model="mail.group" name="message_append_note">
|
||||
<!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
|
||||
<value eval="[ref('mail.group_all_employees')]"/>
|
||||
<value>Module Employee Appraisals has been installed</value>
|
||||
<value>Create evaluations for your subordinates or manager. You can define a plan with several surveys, where you organize evaluation surveys related to the hierarchy levels. Evaluations filled by employees can exported as pdf files.</value>
|
||||
</function>
|
||||
<record model="mail.message" id="module_install_notification">
|
||||
<field name="model">mail.group</field>
|
||||
<field name="res_id" ref="mail.group_all_employees"/>
|
||||
<field name="type">notification</field>
|
||||
<field name="subject">Employee Appraisals application installed!</field>
|
||||
<field name="body">Manage employee reviews: you can define an appraisal campaign with several steps, with specific evaluation surveys according to hierarchy levels.
|
||||
Evaluations filled by employees may be exported as pdf files.</field>
|
||||
</record>
|
||||
|
||||
<record id="survey_2" model="survey">
|
||||
<field name="title">Self Appraisal</field>
|
||||
|
|
|
@ -267,16 +267,27 @@
|
|||
<field name="arch" type="xml">
|
||||
<form string="Interview Appraisal" version="7.0">
|
||||
<header>
|
||||
<button string="Cancel" name="survey_req_cancel" states="draft,waiting_answer" type="object" icon="gtk-cancel"/>
|
||||
<button name="action_print_survey" string="Print Survey" type="object" states="draft" icon="gtk-print" context="{'survey_id': survey_id, 'response_id': [response], 'response_no':0}" attrs="{'readonly':[('survey_id','=',False)]}" class="oe_highlight"/>
|
||||
<button string="Send Request" name="survey_req_waiting_answer" states="draft" type="object" icon="gtk-yes" class="oe_highlight"/>
|
||||
<button name="%(survey.action_view_survey_question_message)d" string="Answer Survey" type="action" states="waiting_answer" context="{'survey_id': survey_id, 'response_id': [response], 'response_no':0, 'active' : response,'request' : True, 'object' : 'hr.evaluation.interview', 'cur_id' : active_id}" attrs="{'readonly':[('survey_id','=',False)]}"/>
|
||||
<button string="Done" name="survey_req_done" states="waiting_answer" type="object" icon="gtk-jump-to"/>
|
||||
<button string="Cancel" name="survey_req_cancel" type="object"
|
||||
states="draft,waiting_answer"/>
|
||||
<button string="Print Survey" name="action_print_survey" type="object"
|
||||
states="draft" context="{'survey_id': survey_id, 'response_id': [response], 'response_no':0}"
|
||||
attrs="{'readonly':[('survey_id','=',False)]}" class="oe_highlight"/>
|
||||
<button string="Send Request" name="survey_req_waiting_answer" type="object"
|
||||
states="draft" class="oe_highlight"/>
|
||||
<button string="Answer Survey" name="%(survey.action_view_survey_question_message)d" type="action"
|
||||
states="waiting_answer"
|
||||
context="{'survey_id': survey_id, 'response_id': [response], 'response_no':0, 'active' : response,'request' : True, 'object' : 'hr.evaluation.interview', 'cur_id' : active_id}"
|
||||
attrs="{'readonly':[('survey_id','=',False)]}"/>
|
||||
<button string="Done" name="survey_req_done" type="object"
|
||||
states="waiting_answer"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="waiting_answer,done"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_right oe_button_box" name="button_box">
|
||||
<button string="Send Reminder Email" name="%(mail.action_email_compose_message_wizard)d" icon="terp-mail-message-new" type="action" states="waiting_answer"/>
|
||||
<button string="Send Reminder Email" name="%(mail.action_email_compose_message_wizard)d" type="action"
|
||||
states="waiting_answer"
|
||||
context="{'default_body_text': 'Hello,\n\nKindly post your response for the survey interview.\n\nThanks',
|
||||
'default_subject': 'Reminder to fill up Survey' }"/>
|
||||
</div>
|
||||
<group>
|
||||
<group col="3" colspan="1">
|
||||
|
@ -389,21 +400,23 @@
|
|||
|
||||
<!-- Email Compose message Action-->
|
||||
<act_window
|
||||
id="evaluation_reminders" name="Appraisal Reminders"
|
||||
res_model="mail.compose.message"
|
||||
src_model="hr.evaluation.interview"
|
||||
view_type="form" view_mode="form"
|
||||
target="new" multi="True"
|
||||
key2="client_action_multi"
|
||||
context="{'mail.compose.message.mode':'mass_mail'}"/>
|
||||
id="evaluation_reminders" name="Appraisal Reminders"
|
||||
res_model="mail.compose.message"
|
||||
src_model="hr.evaluation.interview"
|
||||
view_type="form" view_mode="form"
|
||||
target="new" multi="True"
|
||||
key2="client_action_multi"
|
||||
context="{'default_composition_mode': 'mass_mail',
|
||||
'default_body_text': 'Hello,\n\nKindly post your response for the survey interview.\n\nThanks',
|
||||
'default_subject': 'Reminder to fill up Survey'}"/>
|
||||
|
||||
<!-- Appraisal Interviews Button on Employee Form -->
|
||||
<act_window
|
||||
context="{'search_default_user_to_review_id': [active_id], 'default_user_to_review_id': active_id}"
|
||||
id="act_hr_employee_2_hr__evaluation_interview"
|
||||
name="Appraisal Interviews"
|
||||
res_model="hr.evaluation.interview"
|
||||
src_model="hr.employee"/>
|
||||
context="{'search_default_user_to_review_id': [active_id], 'default_user_to_review_id': active_id}"
|
||||
id="act_hr_employee_2_hr__evaluation_interview"
|
||||
name="Appraisal Interviews"
|
||||
res_model="hr.evaluation.interview"
|
||||
src_model="hr.employee"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
#import hr_evaluation_mail
|
||||
import mail_compose_message
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -1,42 +0,0 @@
|
|||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from osv import fields, osv
|
||||
import tools
|
||||
|
||||
class hr_evaluation_reminder(osv.osv_memory):
|
||||
_name = "hr.evaluation.reminder"
|
||||
_description = "Sends Reminders to employees to fill the evaluations"
|
||||
_columns = {
|
||||
'evaluation_id': fields.many2one('hr.evaluation.interview', 'Interview', required=True)
|
||||
}
|
||||
|
||||
def send_mail(self, cr, uid, ids, context=None):
|
||||
mail_message = self.pool.get('mail.message')
|
||||
hr_evaluation_interview_obj = self.pool.get('hr.evaluation.interview')
|
||||
evaluation_data = self.read(cr, uid, ids, context=context)[0]
|
||||
current_interview = hr_evaluation_interview_obj.browse(cr, uid, evaluation_data.get('evaluation_id'))
|
||||
if current_interview.state == "waiting_answer" and current_interview.user_to_review_id.work_email :
|
||||
msg = " Hello %s, \n\n Kindly post your response for '%s' survey interview. \n\n Thanks," %(current_interview.user_to_review_id.name, current_interview.survey_id.title)
|
||||
mail_message.schedule_with_attach(cr, uid, tools.config['email_from'], [current_interview.user_to_review_id.work_email],\
|
||||
'Reminder to fill up Survey', msg, context=context)
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -1,34 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="view_hr_evaluation_send_mail" model="ir.ui.view">
|
||||
<field name="name">hr.evaluation.send.mail</field>
|
||||
<field name="model">hr.evaluation.reminder</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Evaluation Reminders" version="7.0">
|
||||
<group string="Send Evaluation Reminder">
|
||||
<field name="evaluation_id"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="send_mail" string="Send Mail" type="object" class="oe_highlight"/>
|
||||
or
|
||||
<button string="Cancel" class="oe_link" special="cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_hr_evaluation_send_mail" model="ir.actions.act_window">
|
||||
<field name="name">Evaluation Send Mail</field>
|
||||
<field name="res_model">hr.evaluation.reminder</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="view_id" ref="view_hr_evaluation_send_mail"/>
|
||||
<field name="context">{'record_id':active_id}</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -1,54 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2010-Today OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from osv import osv
|
||||
from osv import fields
|
||||
import tools
|
||||
from tools.translate import _
|
||||
|
||||
class mail_compose_message(osv.osv_memory):
|
||||
_inherit = 'mail.compose.message'
|
||||
|
||||
def get_value(self, cr, uid, model, resource_id, context=None):
|
||||
'''
|
||||
To get values of the resource_id for the model
|
||||
@param model: Object
|
||||
@param resource_id: id of a record for which values to be read
|
||||
|
||||
@return: Returns a dictionary
|
||||
'''
|
||||
if context is None:
|
||||
context = {}
|
||||
result = super(mail_compose_message, self).get_value(cr, uid, model, resource_id, context=context)
|
||||
if model == 'hr.evaluation.interview' and resource_id:
|
||||
model_pool = self.pool.get(model)
|
||||
record_data = model_pool.browse(cr, uid, resource_id, context)
|
||||
if record_data.state == "waiting_answer":
|
||||
msg = _("Hello %s, \n\n Kindly post your response for '%s' survey interview. \n\n Thanks,") %(record_data.user_to_review_id.name, record_data.survey_id.title)
|
||||
result.update({
|
||||
'email_to': record_data.user_to_review_id.work_email or False,
|
||||
'subject': _("Reminder to fill up Survey"),
|
||||
'body_text': msg,
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -21,14 +21,14 @@
|
|||
|
||||
|
||||
{
|
||||
'name': 'Expenses Management',
|
||||
'name': 'Expense Management',
|
||||
'version': '1.0',
|
||||
'category': 'Human Resources',
|
||||
'sequence': 30,
|
||||
'summary': 'Expenses Validation, Invoicing',
|
||||
'description': """
|
||||
This module aims to manage employee's expenses.
|
||||
===============================================
|
||||
This module aims to manage employee expenses.
|
||||
=============================================
|
||||
|
||||
The whole workflow is implemented:
|
||||
----------------------------------
|
||||
|
|
|
@ -2,20 +2,20 @@
|
|||
<openerp>
|
||||
<data noupdate="1">
|
||||
<!-- notify all employees of module installation -->
|
||||
<function model="mail.group" name="message_append_note">
|
||||
<!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
|
||||
<value eval="[ref('mail.group_all_employees')]"/>
|
||||
<value>Module Expenses Management has been installed</value>
|
||||
<value>Manage your employee's expenses, with validations by employee manager and accountant, creation and payment of invoices.
|
||||
<record model="mail.message" id="module_install_notification">
|
||||
<field name="model">mail.group</field>
|
||||
<field name="res_id" ref="mail.group_all_employees"/>
|
||||
<field name="type">notification</field>
|
||||
<field name="subject">Expense Management application installed!</field>
|
||||
<field name="body">Manage your employees' expenses, after due validation by their manager and the accountant, then generate and pay the corresponding invoices.
|
||||
|
||||
This module also uses the analytic accounting and is compatible with the invoice on timesheet module so that you will be able to automatically re-invoice your customer's expenses if your work by project.</value>
|
||||
</function>
|
||||
This feature is also linked to analytic accounting and compatible with timesheet invoices, so you will be able to automatically re-invoice project-related expenses to your customers.</field>
|
||||
</record>
|
||||
|
||||
<!-- Resource: product.uom.categ -->
|
||||
<record id="cat_expense" model="product.category">
|
||||
<field name="parent_id" ref="product.product_category_all"/>
|
||||
<field name="name">Expenses</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
|
||||
{
|
||||
'name': 'Leaves Management',
|
||||
'name': 'Leave Management',
|
||||
'version': '1.5',
|
||||
'author': 'OpenERP SA',
|
||||
'category': 'Human Resources',
|
||||
|
@ -29,8 +29,8 @@
|
|||
'summary': 'Holidays, Allocation and Leave Requests',
|
||||
'website': 'http://www.openerp.com',
|
||||
'description': """
|
||||
This module allows you to manage leaves and leave's requests.
|
||||
=============================================================
|
||||
This module allows you to manage leaves and leave requests.
|
||||
===========================================================
|
||||
|
||||
Implements a dashboard for human resource management that includes:
|
||||
-------------------------------------------------------------------
|
||||
|
|
|
@ -95,7 +95,7 @@ class hr_holidays(osv.osv):
|
|||
_name = "hr.holidays"
|
||||
_description = "Leave"
|
||||
_order = "type desc, date_from asc"
|
||||
_inherit = ['ir.needaction_mixin', 'mail.thread']
|
||||
_inherit = [ 'mail.thread','ir.needaction_mixin']
|
||||
|
||||
def _employee_get(self, cr, uid, context=None):
|
||||
ids = self.pool.get('hr.employee').search(cr, uid, [('user_id', '=', uid)], context=context)
|
||||
|
@ -349,57 +349,48 @@ class hr_holidays(osv.osv):
|
|||
# OpenChatter and notifications
|
||||
# -----------------------------
|
||||
|
||||
def get_needaction_user_ids(self, cr, uid, ids, context=None):
|
||||
result = super(hr_holidays, self).get_needaction_user_ids(cr, uid, ids, context=context)
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
if obj.state == 'confirm' and obj.holiday_type == 'employee' and obj.employee_id.parent_id:
|
||||
result[obj.id] = [obj.employee_id.parent_id.user_id.id]
|
||||
elif obj.state == 'validate1':
|
||||
# get group_hr_manager: everyone will be warned of second validation
|
||||
res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'group_hr_manager') or False
|
||||
obj_id = res and res[1] or False
|
||||
if obj_id:
|
||||
hr_manager_group = self.pool.get('res.groups').read(cr, uid, [obj_id], ['users'], context=context)[0]
|
||||
result[obj.id] = hr_manager_group['users']
|
||||
return result
|
||||
|
||||
def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
|
||||
""" Add 'user_id' and 'manager' to the monitored fields """
|
||||
res = super(hr_holidays, self).message_get_monitored_follower_fields(cr, uid, ids, context=context)
|
||||
return res + ['user_id']
|
||||
def needaction_domain_get(self, cr, uid, ids, context=None):
|
||||
# to be tested, otherwise convert into employee_id in ...
|
||||
emp_obj = self.pool.get('hr.employee')
|
||||
empids = emp_obj.search(cr, uid, [('parent_id.user_id','=',uid)], context=context)
|
||||
dom = [
|
||||
'&', ('state','=','confirm'),('employee_id', 'in', empids)
|
||||
]
|
||||
# if this user is a hr.manager, he should do second validations
|
||||
if self.pool.get('res.users').has_group(cr, uid, 'base.group_hr_manager'):
|
||||
dom = ['|'] + dom + [ ('state','=','validate1') ]
|
||||
return dom
|
||||
|
||||
def create_notificate(self, cr, uid, ids, context=None):
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
self.message_append_note(cr, uid, ids, _('System notification'),
|
||||
_("The request has been <b>created</b> and is waiting confirmation."), type='notification', context=context)
|
||||
self.message_post(cr, uid, ids,
|
||||
_("The request has been <b>created</b> and is waiting confirmation."), context=context)
|
||||
return True
|
||||
|
||||
def holidays_confirm_notificate(self, cr, uid, ids, context=None):
|
||||
for obj in self.browse(cr, uid, ids):
|
||||
self.message_append_note(cr, uid, [obj.id], _('System notification'),
|
||||
_("The request has been <b>submitted</b> and is waiting for validation by the manager."), type='notification')
|
||||
self.message_post(cr, uid, [obj.id],
|
||||
_("The request has been <b>submitted</b> and is waiting for validation by the manager."), context=context)
|
||||
|
||||
def holidays_first_validate_notificate(self, cr, uid, ids, context=None):
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
self.message_append_note(cr, uid, [obj.id], _('System notification'),
|
||||
_("The request has been <b>approved</b>. A second validation is necessary and is now pending."), type='notification', context=context)
|
||||
self.message_post(cr, uid, [obj.id],
|
||||
_("The request has been <b>approved</b>. A second validation is necessary and is now pending."), context=context)
|
||||
|
||||
def holidays_validate_notificate(self, cr, uid, ids, context=None):
|
||||
for obj in self.browse(cr, uid, ids):
|
||||
if obj.double_validation:
|
||||
self.message_append_note(cr, uid, [obj.id], _('System notification'),
|
||||
_("The request has been <b>double validated</b>. The validation process is now over."), type='notification', context=context)
|
||||
self.message_post(cr, uid, [obj.id],
|
||||
_("The request has been <b>double validated</b>. The validation process is now over."), context=context)
|
||||
else:
|
||||
self.message_append_note(cr, uid, [obj.id], _('System notification'),
|
||||
_("The request has been <b>approved</b>. The validation process is now over."), type='notification', context=context)
|
||||
|
||||
self.message_post(cr, uid, [obj.id],
|
||||
_("The request has been <b>approved</b>. The validation process is now over."), context=context)
|
||||
|
||||
def holidays_refuse_notificate(self, cr, uid, ids, context=None):
|
||||
for obj in self.browse(cr, uid, ids):
|
||||
self.message_append_note(cr, uid, [obj.id], _('System notification'),
|
||||
_("The request has been <b>refused</b>. The validation process is now over."), type='notification', context=context)
|
||||
self.message_post(cr, uid, [obj.id],
|
||||
_("The request has been <b>refused</b>. The validation process is now over."), context=context)
|
||||
|
||||
hr_holidays()
|
||||
|
||||
class resource_calendar_leaves(osv.osv):
|
||||
_inherit = "resource.calendar.leaves"
|
||||
|
|
|
@ -13,12 +13,15 @@
|
|||
<field name="sequence">100</field>
|
||||
</record>
|
||||
<!-- notify all employees of module installation -->
|
||||
<function model="mail.group" name="message_append_note">
|
||||
<!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
|
||||
<value eval="[ref('mail.group_all_employees')]"/>
|
||||
<value>Module Leaves Management has been installed</value>
|
||||
<value>Manage employee leaves from the top menu "Human Resources". Employees can create leave requests that are validated by their manager and/or HR people. Once validated, they are visible in the employee's calendar. HR people can define leave types and allocate off-days for employees.</value>
|
||||
</function>
|
||||
<record model="mail.message" id="module_install_notification">
|
||||
<field name="model">mail.group</field>
|
||||
<field name="res_id" ref="mail.group_all_employees"/>
|
||||
<field name="type">notification</field>
|
||||
<field name="subject">Leave Management application installed!</field>
|
||||
<field name="body">Manage employee leaves from the top menu "Human Resources". Employees can create leave requests that are validated by their manager and/or HR officers.
|
||||
Once validated, they are visible in the employee's calendar. HR officers can define leave types and allocate leaves to employees and employee categories.</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- Casual leave -->
|
||||
<record model="hr.holidays.status" id="holiday_status_cl">
|
||||
|
|
|
@ -44,7 +44,6 @@
|
|||
<field name="arch" type="xml">
|
||||
<search string="Search Leave">
|
||||
<field name="date_from"/>
|
||||
<filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="needaction_pending" domain="[('needaction_pending','=',True)]"/>
|
||||
<separator/>
|
||||
<filter icon="terp-check" domain="[('state','=','draft')]" string="To Confirm"/>
|
||||
<filter icon="terp-camera_test" domain="[('state','=','confirm')]" string="To Approve" name="approve"/>
|
||||
|
@ -170,8 +169,7 @@
|
|||
<field name="name">hr.holidays.allocation.tree</field>
|
||||
<field name="model">hr.holidays</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree fonts="bold:needaction_pending==True" colors="red:state == 'refuse';blue:state == 'draft';black:state in ('confirm','validate','validate1')" string="Allocation Requests">
|
||||
<field name="needaction_pending" invisible="1"/>
|
||||
<tree colors="red:state == 'refuse';blue:state == 'draft';black:state in ('confirm','validate','validate1')" string="Allocation Requests">
|
||||
<field name="employee_id"/>
|
||||
<field name="holiday_type"/>
|
||||
<field name="category_id"/>
|
||||
|
@ -218,8 +216,7 @@
|
|||
<field name="model">hr.holidays</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree fonts="bold:needaction_pending==True" colors="red:state == 'refuse';blue:state == ' draft';black:state in ('confirm','validate','validate1')" string="Leaves Summary">
|
||||
<field name="needaction_pending" invisible="1"/>
|
||||
<tree colors="red:state == 'refuse';blue:state == ' draft';black:state in ('confirm','validate','validate1')" string="Leaves Summary">
|
||||
<field name="employee_id"/>
|
||||
<field name="category_id" invisible="1"/>
|
||||
<field name="department_id" invisible="1"/>
|
||||
|
@ -241,8 +238,7 @@
|
|||
<field name="name">hr.holidays.tree</field>
|
||||
<field name="model">hr.holidays</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree fonts="bold:needaction_pending==True" colors="red:state == 'refuse';blue:state == ' draft';black:state in ('confirm','validate','validate1')" string="Leave Requests">
|
||||
<field name="needaction_pending" invisible="1"/>
|
||||
<tree colors="red:state == 'refuse';blue:state == ' draft';black:state in ('confirm','validate','validate1')" string="Leave Requests">
|
||||
<field name="employee_id"/>
|
||||
<field name="holiday_type" string="Mode" groups="base.group_hr_user"/>
|
||||
<field name="name"/>
|
||||
|
|
|
@ -90,8 +90,7 @@ class hr_applicant(base_stage, osv.Model):
|
|||
_name = "hr.applicant"
|
||||
_description = "Applicant"
|
||||
_order = "id desc"
|
||||
_inherit = ['ir.needaction_mixin', 'mail.thread']
|
||||
_mail_compose_message = True
|
||||
_inherit = ['mail.thread', 'ir.needaction_mixin']
|
||||
|
||||
def _get_default_department_id(self, cr, uid, context=None):
|
||||
""" Gives default department by checking if present in the context """
|
||||
|
@ -161,13 +160,11 @@ class hr_applicant(base_stage, osv.Model):
|
|||
date_create = datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S")
|
||||
date_open = datetime.strptime(issue.date_open, "%Y-%m-%d %H:%M:%S")
|
||||
ans = date_open - date_create
|
||||
date_until = issue.date_open
|
||||
|
||||
elif field in ['day_close']:
|
||||
if issue.date_closed:
|
||||
date_create = datetime.strptime(issue.create_date, "%Y-%m-%d %H:%M:%S")
|
||||
date_close = datetime.strptime(issue.date_closed, "%Y-%m-%d %H:%M:%S")
|
||||
date_until = issue.date_closed
|
||||
ans = date_close - date_create
|
||||
if ans:
|
||||
duration = float(ans.days)
|
||||
|
@ -328,14 +325,13 @@ class hr_applicant(base_stage, osv.Model):
|
|||
if custom_values is None: custom_values = {}
|
||||
custom_values.update({
|
||||
'name': msg.get('subject') or _("No Subject"),
|
||||
'description': msg.get('body_text'),
|
||||
'description': msg.get('body'),
|
||||
'email_from': msg.get('from'),
|
||||
'email_cc': msg.get('cc'),
|
||||
'user_id': False,
|
||||
})
|
||||
if msg.get('priority'):
|
||||
custom_values['priority'] = msg.get('priority')
|
||||
custom_values.update(self.message_partner_by_email(cr, uid, msg.get('from', False), context=context))
|
||||
return super(hr_applicant,self).message_new(cr, uid, msg, custom_values=custom_values, context=context)
|
||||
|
||||
def message_update(self, cr, uid, ids, msg, update_vals=None, context=None):
|
||||
|
@ -360,7 +356,7 @@ class hr_applicant(base_stage, osv.Model):
|
|||
'revenue': 'planned_revenue',
|
||||
'probability': 'probability',
|
||||
}
|
||||
for line in msg.get('body_text', '').split('\n'):
|
||||
for line in msg.get('body', '').split('\n'):
|
||||
line = line.strip()
|
||||
res = tools.misc.command_re.match(line)
|
||||
if res and maps.get(res.group(1).lower(), False):
|
||||
|
@ -457,23 +453,18 @@ class hr_applicant(base_stage, osv.Model):
|
|||
# OpenChatter methods and notifications
|
||||
# -------------------------------------------------------
|
||||
|
||||
def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
|
||||
""" Add 'user_id' to the monitored fields """
|
||||
res = super(hr_applicant, self).message_get_monitored_follower_fields(cr, uid, ids, context=context)
|
||||
return res + ['user_id']
|
||||
|
||||
def stage_set_send_note(self, cr, uid, ids, stage_id, context=None):
|
||||
""" Override of the (void) default notification method. """
|
||||
if not stage_id: return True
|
||||
stage_name = self.pool.get('hr.recruitment.stage').name_get(cr, uid, [stage_id], context=context)[0][1]
|
||||
return self.message_append_note(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context)
|
||||
return self.message_post(cr, uid, ids, body= _("Stage changed to <b>%s</b>.") % (stage_name), context=context)
|
||||
|
||||
def case_get_note_msg_prefix(self, cr, uid, id, context=None):
|
||||
return 'Applicant'
|
||||
|
||||
def case_open_send_note(self, cr, uid, ids, context=None):
|
||||
message = _("Applicant has been set <b>in progress</b>.")
|
||||
return self.message_append_note(cr, uid, ids, body=message, context=context)
|
||||
return self.message_post(cr, uid, ids, body=message, context=context)
|
||||
|
||||
def case_close_send_note(self, cr, uid, ids, context=None):
|
||||
if context is None:
|
||||
|
@ -481,23 +472,23 @@ class hr_applicant(base_stage, osv.Model):
|
|||
for applicant in self.browse(cr, uid, ids, context=context):
|
||||
if applicant.emp_id:
|
||||
message = _("Applicant has been <b>hired</b> and created as an employee.")
|
||||
self.message_append_note(cr, uid, [applicant.id], body=message, context=context)
|
||||
self.message_post(cr, uid, [applicant.id], body=message, context=context)
|
||||
else:
|
||||
message = _("Applicant has been <b>hired</b>.")
|
||||
self.message_append_note(cr, uid, [applicant.id], body=message, context=context)
|
||||
self.message_post(cr, uid, [applicant.id], body=message, context=context)
|
||||
return True
|
||||
|
||||
def case_cancel_send_note(self, cr, uid, ids, context=None):
|
||||
msg = 'Applicant <b>refused</b>.'
|
||||
return self.message_append_note(cr, uid, ids, body=msg, context=context)
|
||||
return self.message_post(cr, uid, ids, body=msg, context=context)
|
||||
|
||||
def case_reset_send_note(self, cr, uid, ids, context=None):
|
||||
message =_("Applicant has been set as <b>new</b>.")
|
||||
return self.message_append_note(cr, uid, ids, body=message, context=context)
|
||||
return self.message_post(cr, uid, ids, body=message, context=context)
|
||||
|
||||
def create_send_note(self, cr, uid, ids, context=None):
|
||||
message = _("Applicant has been <b>created</b>.")
|
||||
return self.message_append_note(cr, uid, ids, body=message, context=context)
|
||||
return self.message_post(cr, uid, ids, body=message, context=context)
|
||||
|
||||
|
||||
class hr_job(osv.osv):
|
||||
|
|
|
@ -2,14 +2,15 @@
|
|||
<openerp>
|
||||
<data noupdate="1">
|
||||
<!-- notify all employees of module installation -->
|
||||
<function model="mail.group" name="message_append_note">
|
||||
<!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
|
||||
<value eval="[ref('mail.group_all_employees')]"/>
|
||||
<value>Module Recruitment Process has been installed</value>
|
||||
<value>Manage job positions and the recruitment process in your company. This module is integrated with the module Survey to help you to define interviews for different jobs.
|
||||
<record model="mail.message" id="module_install_notification">
|
||||
<field name="model">mail.group</field>
|
||||
<field name="res_id" ref="mail.group_all_employees"/>
|
||||
<field name="type">notification</field>
|
||||
<field name="subject">Recruitment Process application installed!</field>
|
||||
<field name="body">Manage job positions and your company's recruitment process. This application is integrated with the Survey application to help you define interviews for different jobs.
|
||||
|
||||
You can automatically create application records from an email gateway, that you can configure in the Human Resources Settings.</value>
|
||||
</function>
|
||||
You can automatically receive job application though an email gateway, see the Human Resources settings.</field>
|
||||
</record>
|
||||
|
||||
<!-- Meeting Types (for interview meetings) -->
|
||||
<record model="crm.meeting.type" id="categ_meet_interview">
|
||||
|
|
|
@ -76,8 +76,8 @@
|
|||
<field name="name">Applicants</field>
|
||||
<field name="model">hr.applicant</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Applicants" fonts="bold:needaction_pending==True" colors="grey:state in ('cancel','done');blue:state=='pending'">
|
||||
<field name="needaction_pending" invisible="1"/>
|
||||
<tree string="Applicants" fonts="bold:message_unread==True" colors="grey:state in ('cancel','done');blue:state=='pending'">
|
||||
<field name="message_unread" invisible="1"/>
|
||||
<field name="create_date" groups="base.group_no_one"/>
|
||||
<field name="name" string="Subject"/>
|
||||
<field name="partner_name"/>
|
||||
|
@ -208,7 +208,7 @@
|
|||
<field name="arch" type="xml">
|
||||
<search string="Search Jobs">
|
||||
<field name="partner_name" filter_domain="['|','|',('name','ilike',self),('partner_name','ilike',self),('email_from','ilike',self)]" string="Subject / Applicant"/>
|
||||
<filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="needaction_pending" domain="[('needaction_pending','=',True)]"/>
|
||||
<filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/>
|
||||
<separator/>
|
||||
<filter icon="terp-document-new" string="New" domain="[('state','=','draft')]" help="All Initial Jobs"/>
|
||||
<filter icon="terp-camera_test" string="In Progress" domain="[('state','=','open')]" help="Open Jobs"/>
|
||||
|
|
|
@ -95,25 +95,25 @@ class account_analytic_account(osv.osv):
|
|||
def set_close(self, cr, uid, ids, context=None):
|
||||
self.write(cr, uid, ids, {'state':'close'}, context=context)
|
||||
message = _("Contract has been <b>closed</b>.")
|
||||
self.message_append_note(cr, uid, ids, body=message, context=context)
|
||||
self.message_post(cr, uid, ids, body=message, context=context)
|
||||
return True
|
||||
|
||||
def set_cancel(self, cr, uid, ids, context=None):
|
||||
self.write(cr, uid, ids, {'state':'cancelled'}, context=context)
|
||||
message = _("Contract has been <b>cancelled</b>.")
|
||||
self.message_append_note(cr, uid, ids, body=message, context=context)
|
||||
self.message_post(cr, uid, ids, body=message, context=context)
|
||||
return True
|
||||
|
||||
def set_open(self, cr, uid, ids, context=None):
|
||||
self.write(cr, uid, ids, {'state':'open'}, context=context)
|
||||
message = _("Contract has been <b>opened</b>.")
|
||||
self.message_append_note(cr, uid, ids, body=message, context=context)
|
||||
self.message_post(cr, uid, ids, body=message, context=context)
|
||||
return True
|
||||
|
||||
def set_pending(self, cr, uid, ids, context=None):
|
||||
self.write(cr, uid, ids, {'state':'pending'}, context=context)
|
||||
message = _("Contract has been set as <b>pending</b>.")
|
||||
self.message_append_note(cr, uid, ids, body=message, context=context)
|
||||
self.message_post(cr, uid, ids, body=message, context=context)
|
||||
return True
|
||||
|
||||
account_analytic_account()
|
||||
|
|
|
@ -27,8 +27,8 @@
|
|||
'sequence': 16,
|
||||
'summary': 'Timesheets, Attendances, Activities',
|
||||
'description': """
|
||||
This module helps you to easily encode and validate timesheet and attendances within the same view.
|
||||
===================================================================================================
|
||||
This module helps you to easily record and validate timesheets and attendances within the same view.
|
||||
====================================================================================================
|
||||
|
||||
* It will maintain attendances and track (sign in/sign out) events.
|
||||
* Track the timesheet lines.
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
<?xml version="1.0" ?>
|
||||
<openerp>
|
||||
<data noupdate="1">
|
||||
|
||||
<!-- Notify all employees of module installation -->
|
||||
<function model="mail.group" name="message_append_note">
|
||||
<!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
|
||||
<value eval="[ref('mail.group_all_employees')]"/>
|
||||
<value>Module Timesheets Validation has been installed</value>
|
||||
<value>From the top menu "Human Resources", encode and validate timesheets and attendances.</value>
|
||||
</function>
|
||||
<!-- notify all employees of module installation -->
|
||||
<record model="mail.message" id="hr_timesheet_module_install_notification">
|
||||
<field name="model">mail.group</field>
|
||||
<field name="res_id" ref="mail.group_all_employees"/>
|
||||
<field name="type">notification</field>
|
||||
<field name="subject">Timesheet Validation application installed!</field>
|
||||
<field name="body">From the top menu "Human Resources", enter and validate timesheets and attendances.</field>
|
||||
</record>
|
||||
|
||||
<record id="ir_actions_server_timsheet_sheet" model="ir.actions.server">
|
||||
<field eval="5" name="sequence"/>
|
||||
<field eval=""""code"""" name="state"/>
|
||||
<field eval=""""ir.actions.server"""" name="type"/>
|
||||
<field name="sequence" eval="5"/>
|
||||
<field name="state">code</field>
|
||||
<field name="type">ir.actions.server</field>
|
||||
<field name="model_id" ref="model_hr_timesheet_current_open"/>
|
||||
<field eval="[(6,0,[])]" name="child_ids"/>
|
||||
<field eval=""""action = pool.get('hr.timesheet.current.open').open_timesheet(cr, uid, None, context)"""" name="code"/>
|
||||
<field eval=""""True"""" name="condition"/>
|
||||
<field eval=""""My Timesheet"""" name="name"/>
|
||||
<field name="code">action = pool.get('hr.timesheet.current.open').open_timesheet(cr, uid, None, context)</field>
|
||||
<field name="condition">True</field>
|
||||
<field name="name">My Timesheet</field>
|
||||
</record>
|
||||
|
||||
<menuitem name="My Current Timesheet" id="menu_act_hr_timesheet_sheet_form_my_current" parent="hr_attendance.menu_hr_time_tracking" action="ir_actions_server_timsheet_sheet" sequence="1"/>
|
||||
|
|
|
@ -67,21 +67,21 @@ class idea_idea(osv.osv):
|
|||
|
||||
def idea_cancel(self, cr, uid, ids, context={}):
|
||||
self.write(cr, uid, ids, { 'state': 'cancel' })
|
||||
self.message_append_note(cr, uid, ids, body=_('Idea canceled.'), context=context)
|
||||
self.message_post(cr, uid, ids, body=_('Idea canceled.'), context=context)
|
||||
return True
|
||||
|
||||
def idea_open(self, cr, uid, ids, context={}):
|
||||
self.write(cr, uid, ids, { 'state': 'open'})
|
||||
self.message_append_note(cr, uid, ids, body=_('Idea accepted.'), context=context)
|
||||
self.message_post(cr, uid, ids, body=_('Idea accepted.'), context=context)
|
||||
return True
|
||||
|
||||
def idea_close(self, cr, uid, ids, context={}):
|
||||
self.message_append_note(cr, uid, ids, body=_('Idea done.'), context=context)
|
||||
self.message_post(cr, uid, ids, body=_('Idea done.'), context=context)
|
||||
self.write(cr, uid, ids, { 'state': 'close' })
|
||||
return True
|
||||
|
||||
def idea_draft(self, cr, uid, ids, context={}):
|
||||
self.message_append_note(cr, uid, ids, body=_('Idea reset to draft.'), context=context)
|
||||
self.message_post(cr, uid, ids, body=_('Idea reset to draft.'), context=context)
|
||||
self.write(cr, uid, ids, { 'state': 'draft' })
|
||||
return True
|
||||
idea_idea()
|
||||
|
|
|
@ -425,7 +425,7 @@ class import_framework(Thread):
|
|||
email_id = email_obj.create(self.cr, self.uid, {
|
||||
'email_from' : 'import@module.openerp',
|
||||
'email_to' : self.email,
|
||||
'body_text' : self.get_email_body(result, error),
|
||||
'body' : self.get_email_body(result, error),
|
||||
'subject' : self.get_email_subject(result, error),
|
||||
'auto_delete' : True})
|
||||
email_obj.send(self.cr, self.uid, [email_id])
|
||||
|
|
|
@ -226,7 +226,7 @@ class sugar_import(import_framework):
|
|||
'model': 'model',
|
||||
'partner_id/.id': 'partner_id/.id',
|
||||
'user_id/id': ref(self.TABLE_USER, 'assigned_user_id'),
|
||||
'body_text': 'description',
|
||||
'body': 'description',
|
||||
'body_html' : 'description_html',
|
||||
|
||||
}
|
||||
|
|
|
@ -20,11 +20,11 @@
|
|||
##############################################################################
|
||||
|
||||
import mail_alias
|
||||
import mail_message
|
||||
import mail_followers
|
||||
import mail_message
|
||||
import mail_mail
|
||||
import mail_thread
|
||||
import mail_group
|
||||
import ir_needaction
|
||||
import res_partner
|
||||
import res_users
|
||||
import report
|
||||
|
|
|
@ -64,6 +64,7 @@ The main features of the module are:
|
|||
'wizard/mail_compose_message_view.xml',
|
||||
'res_config_view.xml',
|
||||
'mail_message_view.xml',
|
||||
'mail_mail_view.xml',
|
||||
'mail_followers_view.xml',
|
||||
'mail_thread_view.xml',
|
||||
'mail_group_view.xml',
|
||||
|
|
|
@ -3,49 +3,43 @@
|
|||
<data noupdate="1">
|
||||
|
||||
<record id="message_blogpost0" model="mail.message">
|
||||
<field name="subject">Internal company announce</field>
|
||||
<field name="model">mail.group</field>
|
||||
<field name="res_id" ref="group_all_employees"/>
|
||||
<field name="content_subtype">html</field>
|
||||
<field name="body_html"><![CDATA[Your monthly meal vouchers arrived. You can get them at Christine's office.
|
||||
This month you also have 250 EUR of eco-checks for all employees that worked with us since 1 year minimum.]]></field>
|
||||
<field name="res_id" ref="mail.group_all_employees"/>
|
||||
<field name="body">Your monthly meal vouchers arrived. You can get them at Christine's office.
|
||||
This month you also get 250 EUR of eco-vouchers if you have been in the company for more than a year.</field>
|
||||
<field name="type">comment</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
</record>
|
||||
|
||||
<record id="message_blogpost0_comment0" model="mail.message">
|
||||
<field name="model">mail.group</field>
|
||||
<field name="res_id" ref="group_all_employees"/>
|
||||
<field name="content_subtype">html</field>
|
||||
<field name="body_html"><![CDATA[Great.]]></field>
|
||||
<field name="body"><![CDATA[Great.]]></field>
|
||||
<field name="parent_id" ref="message_blogpost0"/>
|
||||
<field name="type">comment</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
</record>
|
||||
|
||||
<record id="message_blogpost0_comment1" model="mail.message">
|
||||
<field name="model">mail.group</field>
|
||||
<field name="res_id" ref="group_all_employees"/>
|
||||
<field name="content_subtype">html</field>
|
||||
<field name="body_html"><![CDATA[Yes, that's a good news.]]></field>
|
||||
<field name="body">Thanks, but where is Christine's office, if I may ask? (I'm new here)</field>
|
||||
<field name="parent_id" ref="message_blogpost0"/>
|
||||
<field name="type">comment</field>
|
||||
<field name="user_id" ref="base.user_demo"/>
|
||||
</record>
|
||||
|
||||
<record id="message_blogpost0_comment2" model="mail.message">
|
||||
<field name="model">mail.group</field>
|
||||
<field name="res_id" ref="group_all_employees"/>
|
||||
<field name="content_subtype">html</field>
|
||||
<field name="body_html"><![CDATA[Sure: Curabitur tempor bibendum diam, et euismod ante rutrum vel.
|
||||
|
||||
In quis purus neque. Integer sodales dolor eu elit fringilla blandit. Maecenas lacus nisi, facilisis sit amet viverra eu, tristique vel augue.
|
||||
Donec viverra congue dui eu blandit. In lacinia molestie nulla, ut sagittis risus feugiat eu.
|
||||
|
||||
Check the file in attachment for more information ! (third comment)]]></field>
|
||||
<field name="body">Great news, I need to buy a new fridge, I think I can pay it with the eco-vouchers!</field>
|
||||
<field name="parent_id" ref="message_blogpost0"/>
|
||||
<field name="type">comment</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
</record>
|
||||
|
||||
<record id="message_blogpost0_comment1_2" model="mail.message">
|
||||
<field name="model">mail.group</field>
|
||||
<field name="res_id" ref="group_all_employees"/>
|
||||
<field name="body">Building B3, second floor on the right :-)</field>
|
||||
<field name="parent_id" ref="message_blogpost0_comment1"/>
|
||||
<field name="type">comment</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.config_parameter" id="user_mail_alias">
|
||||
|
|
|
@ -13,16 +13,16 @@
|
|||
</record>
|
||||
|
||||
<!-- notify all employees of module installation -->
|
||||
<function model="mail.group" name="message_append_note">
|
||||
<!-- ids, subject, body, parent_id=False, type='notification', content_subtype='html' -->
|
||||
<value eval="[ref('mail.group_all_employees')]"/>
|
||||
<value>Welcome to OpenERP!</value>
|
||||
<value>Your homepage is a summary of messages you received and key information about documents you follow.
|
||||
<record model="mail.message" id="module_install_notification">
|
||||
<field name="model">mail.group</field>
|
||||
<field name="res_id" ref="mail.group_all_employees"/>
|
||||
<field name="type">notification</field>
|
||||
<field name="subject">Welcome to OpenERP!</field>
|
||||
<field name="body">Your homepage is a summary of messages you received and key information about documents you follow.
|
||||
|
||||
The top menu bar contains all applications you installed. You can use this <i>Settings</i> menu to install more applications, activate others features or give access to new users.
|
||||
|
||||
To setup your preferences (name, email signature, avatar), click on the top right corner.</value>
|
||||
</function>
|
||||
|
||||
To setup your preferences (name, email signature, avatar), click on the top right corner.</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -57,7 +57,7 @@ Use the thread viewer widget inside your form view by using the mail_thread widg
|
|||
Send notifications
|
||||
+++++++++++++++++++
|
||||
|
||||
When sending a notification is required in your workflow or business logic, use the ``message_append_note`` method. This method is a shortcut to the ``message_append`` method that takes all ``mail.message`` fields as arguments. This latter method calls ``message_create`` that
|
||||
When sending a notification is required in your workflow or business logic, use the ``message_post`` method. This method is a shortcut to the ``message_append`` method that takes all ``mail.message`` fields as arguments. This latter method calls ``message_create`` that
|
||||
|
||||
- creates the message
|
||||
- parses the body to find users you want to push the message to (finding and parsing ``@login`` in the message body)
|
||||
|
@ -74,7 +74,7 @@ You should therefore not worry about subscriptions or anything else than sending
|
|||
return res
|
||||
|
||||
def do_something_send_note(self, cr, uid, ids, context=None):
|
||||
self.message_append_note(cr, uid, ids, _('My subject'),
|
||||
self.message_post(cr, uid, ids, _('My subject'),
|
||||
_("has received a <b>notification</b> and is happy for it."), context=context)
|
||||
|
||||
Notifications guidelines
|
||||
|
@ -147,7 +147,7 @@ The best way to direct the messages that will be displayed in the OpenChatter wi
|
|||
# add: search in the current task project messages
|
||||
'&', '&', ('res_id', '=', my_task.project_id.id), ('model', '=', 'project.project'),
|
||||
# ... containing the task name
|
||||
'|', ('body_text', 'like', '%s' % (my_task.name)), ('body_html', 'like', '%s' % (my_task.name))
|
||||
'|', ('body', 'like', '%s' % (my_task.name)), ('body_html', 'like', '%s' % (my_task.name))
|
||||
] + domain, limit=limit, offset=offset, context=context)
|
||||
# if asked: add ancestor ids to have complete threads
|
||||
if (ascent): msg_ids = self._message_add_ancestor_ids(cr, uid, ids, msg_ids, root_ids, context=context)
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
.. _mail_state:
|
||||
|
||||
message_state
|
||||
message_unread
|
||||
=============
|
||||
|
||||
``message_state`` is a boolean field that states whether the document
|
||||
``message_unread`` is a boolean field that states whether the document
|
||||
has unread messages. In previous versions, some documents were going
|
||||
back to ``pending`` state when receiving an email through the mail
|
||||
gateway. Now the state related to messages differs from the state or
|
||||
stage of the document itself.
|
||||
|
||||
message_state and need action mechanism
|
||||
message_unread and need action mechanism
|
||||
+++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
The ``mail`` module introduces a default behavior for the need_action
|
||||
|
@ -23,6 +23,6 @@ mechanism [REF].
|
|||
"""
|
||||
result = super(ir_needaction_mixin, self).get_needaction_user_ids(cr, uid, ids, context=context)
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
if obj.message_state == False and obj.user_id:
|
||||
if obj.message_unread == False and obj.user_id:
|
||||
result[obj.id].append(obj.user_id.id)
|
||||
return result
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2012-today OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from osv import osv
|
||||
|
||||
class ir_needaction_mixin(osv.Model):
|
||||
""" Update of ir.needaction_mixin class
|
||||
- override the get_needaction_user_ids method to define the default
|
||||
mail gateway need_action: when the object is unread, the object
|
||||
responsible has an action to perform.
|
||||
"""
|
||||
_name = 'ir.needaction_mixin'
|
||||
_inherit = ['ir.needaction_mixin']
|
||||
|
||||
def get_needaction_user_ids(self, cr, uid, ids, context=None):
|
||||
""" Returns the user_ids that have to perform an action. It the
|
||||
document mail state is unread (False), return object.user_id.id
|
||||
as need_action uid.
|
||||
:return: dict { record_id: [user_ids], }
|
||||
"""
|
||||
result = super(ir_needaction_mixin, self).get_needaction_user_ids(cr, uid, ids, context=context)
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
if obj.message_state == False and obj.user_id:
|
||||
result[obj.id].append(obj.user_id.id)
|
||||
return result
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -21,43 +21,113 @@
|
|||
|
||||
from osv import osv
|
||||
from osv import fields
|
||||
import tools
|
||||
|
||||
class mail_followers(osv.Model):
|
||||
""" mail_followers holds the data related to the follow mechanism inside
|
||||
OpenERP. Users can choose to follow documents (records) of any kind that
|
||||
inherits from mail.thread. Following documents allow to receive
|
||||
notifications for new messages.
|
||||
A subscription is characterized by:
|
||||
:param: res_model: model of the followed objects
|
||||
:param: res_id: ID of resource (may be 0 for every objects)
|
||||
:param: user_id: user_id of the follower
|
||||
OpenERP. Partners can choose to follow documents (records) of any kind
|
||||
that inherits from mail.thread. Following documents allow to receive
|
||||
notifications for new messages.
|
||||
A subscription is characterized by:
|
||||
:param: res_model: model of the followed objects
|
||||
:param: res_id: ID of resource (may be 0 for every objects)
|
||||
"""
|
||||
_name = 'mail.followers'
|
||||
_rec_name = 'id'
|
||||
_rec_name = 'partner_id'
|
||||
_log_access = False
|
||||
_order = 'res_model asc'
|
||||
_description = 'Mail Document Followers'
|
||||
_description = 'Document Followers'
|
||||
_columns = {
|
||||
'res_model': fields.char('Related Document Model', size=128,
|
||||
required=True, select=1,
|
||||
help='Model of the followed resource'),
|
||||
'res_id': fields.integer('Related Document ID', select=1,
|
||||
help='Id of the followed resource'),
|
||||
'user_id': fields.many2one('res.users', string='Related User',
|
||||
'partner_id': fields.many2one('res.partner', string='Related Partner',
|
||||
ondelete='cascade', required=True, select=1),
|
||||
}
|
||||
|
||||
|
||||
class mail_notification(osv.Model):
|
||||
""" mail_notification is a relational table modeling messages pushed to users.
|
||||
"""
|
||||
""" Class holding notifications pushed to partners. Followers and partners
|
||||
added in 'contacts to notify' receive notifications. """
|
||||
_name = 'mail.notification'
|
||||
_rec_name = 'id'
|
||||
_rec_name = 'partner_id'
|
||||
_log_access = False
|
||||
_order = 'message_id desc'
|
||||
_description = 'Mail notification'
|
||||
_description = 'Notifications'
|
||||
|
||||
_columns = {
|
||||
'user_id': fields.many2one('res.users', string='User',
|
||||
ondelete='cascade', required=True, select=1),
|
||||
'partner_id': fields.many2one('res.partner', string='Contact',
|
||||
ondelete='cascade', required=True),
|
||||
'read': fields.boolean('Read'),
|
||||
'message_id': fields.many2one('mail.message', string='Message',
|
||||
ondelete='cascade', required=True, select=1),
|
||||
ondelete='cascade', required=True),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'read': False,
|
||||
}
|
||||
|
||||
def init(self, cr):
|
||||
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('mail_notification_partner_id_read_message_id',))
|
||||
if not cr.fetchone():
|
||||
cr.execute('CREATE INDEX mail_notification_partner_id_read_message_id ON mail_notification (partner_id, read, message_id)')
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
""" Override of create to check that we can not create a notification
|
||||
for a message the user can not read. """
|
||||
if self.pool.get('mail.message').check_access_rights(cr, uid, 'read'):
|
||||
return super(mail_notification, self).create(cr, uid, vals, context=context)
|
||||
return False
|
||||
|
||||
def set_message_read(self, cr, uid, msg_id, context=None):
|
||||
partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
|
||||
notif_ids = self.search(cr, uid, [('partner_id', '=', partner_id), ('message_id', '=', msg_id)], context=context)
|
||||
return self.write(cr, uid, notif_ids, {'read': True}, context=context)
|
||||
|
||||
def notify(self, cr, uid, partner_ids, msg_id, context=None):
|
||||
""" Send by email the notification depending on the user preferences """
|
||||
context = context or {}
|
||||
# mail_noemail (do not send email) or no partner_ids: do not send, return
|
||||
if context.get('mail_noemail') or not partner_ids:
|
||||
return True
|
||||
|
||||
mail_mail = self.pool.get('mail.mail')
|
||||
msg = self.pool.get('mail.message').browse(cr, uid, msg_id, context=context)
|
||||
|
||||
# add signature
|
||||
body_html = msg.body
|
||||
signature = msg.author_id and msg.author_id.user_ids[0].signature or ''
|
||||
if signature:
|
||||
body_html = tools.append_content_to_html(body_html, signature)
|
||||
|
||||
mail_values = {
|
||||
'mail_message_id': msg.id,
|
||||
'email_to': [],
|
||||
'auto_delete': True,
|
||||
'body_html': body_html,
|
||||
'state': 'outgoing',
|
||||
}
|
||||
|
||||
for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids, context=context):
|
||||
# Do not send an email to the writer
|
||||
if partner.user_ids and partner.user_ids[0].id == uid:
|
||||
continue
|
||||
# Do not send to partners without email address defined
|
||||
if not partner.email:
|
||||
continue
|
||||
# Partner does not want to receive any emails
|
||||
if partner.notification_email_send == 'none':
|
||||
continue
|
||||
# Partner wants to receive only emails and comments
|
||||
if partner.notification_email_send == 'comment' and msg.type not in ('email', 'comment'):
|
||||
continue
|
||||
# Partner wants to receive only emails
|
||||
if partner.notification_email_send == 'email' and msg.type != 'email':
|
||||
continue
|
||||
if partner.email not in mail_values['email_to']:
|
||||
mail_values['email_to'].append(partner.email)
|
||||
if mail_values['email_to']:
|
||||
mail_values['email_to'] = ', '.join(mail_values['email_to'])
|
||||
email_notif_id = mail_mail.create(cr, uid, mail_values, context=context)
|
||||
mail_mail.send(cr, uid, [email_notif_id], context=context)
|
||||
return True
|
||||
|
|
|
@ -6,13 +6,12 @@
|
|||
<record model="ir.ui.view" id="view_followers_tree">
|
||||
<field name="name">mail.followers.tree</field>
|
||||
<field name="model">mail.followers</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="priority">10</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Followers">
|
||||
<field name="res_model"/>
|
||||
<field name="res_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="partner_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
@ -24,7 +23,7 @@
|
|||
<field name="priority">10</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Notifications">
|
||||
<field name="user_id"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="message_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
|
@ -38,7 +37,7 @@
|
|||
</record>
|
||||
|
||||
<record id="action_view_notifications" model="ir.actions.act_window">
|
||||
<field name="name">Pushed notif</field>
|
||||
<field name="name">Notifications</field>
|
||||
<field name="res_model">mail.notification</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
|
|
|
@ -28,13 +28,8 @@ from osv import fields
|
|||
from tools.translate import _
|
||||
|
||||
class mail_group(osv.Model):
|
||||
"""
|
||||
A mail_group is a collection of users sharing messages in a discussion
|
||||
group. Group users are users that follow the mail group, using the
|
||||
subscription/follow mechanism of OpenSocial. A mail group has nothing
|
||||
in common with res.users.group.
|
||||
"""
|
||||
|
||||
""" A mail_group is a collection of users sharing messages in a discussion
|
||||
group. The group mechanics are based on the followers. """
|
||||
_description = 'Discussion group'
|
||||
_name = 'mail.group'
|
||||
_inherit = ['mail.thread']
|
||||
|
@ -49,27 +44,12 @@ class mail_group(osv.Model):
|
|||
def _set_image(self, cr, uid, id, name, value, args, context=None):
|
||||
return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context)
|
||||
|
||||
def _get_last_month_msg_nbr(self, cr, uid, ids, name, args, context=None):
|
||||
result = {}
|
||||
for id in ids:
|
||||
lower_date = (DT.datetime.now() - DT.timedelta(days=30)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
|
||||
result[id] = self.message_search(cr, uid, [id], limit=None, domain=[('date', '>=', lower_date)], count=True, context=context)
|
||||
return result
|
||||
|
||||
def _get_default_image(self, cr, uid, context=None):
|
||||
image_path = openerp.modules.get_module_resource('mail', 'static/src/img', 'groupdefault.png')
|
||||
return tools.image_resize_image_big(open(image_path, 'rb').read().encode('base64'))
|
||||
|
||||
_columns = {
|
||||
'description': fields.text('Description'),
|
||||
'menu_id': fields.many2one('ir.ui.menu', string='Related Menu', required=True, ondelete="cascade"),
|
||||
'responsible_id': fields.many2one('res.users', string='Responsible',
|
||||
ondelete='set null', required=True, select=1,
|
||||
help="Responsible of the group that has all rights on the record."),
|
||||
'public': fields.selection([('public', 'Public'), ('private', 'Private'), ('groups', 'Selected Group Only')],
|
||||
string='Privacy', required=True,
|
||||
help='This group is visible by non members. '\
|
||||
'Invisible groups can add members through the invite button.'),
|
||||
'public': fields.selection([('public','Public'),('private','Private'),('groups','Selected Group Only')], 'Privacy', required=True,
|
||||
help='This group is visible by non members. \
|
||||
Invisible groups can add members through the invite button.'),
|
||||
'group_public_id': fields.many2one('res.groups', string='Authorized Group'),
|
||||
'group_ids': fields.many2many('res.groups', rel='mail_group_res_group_rel',
|
||||
id1='mail_group_id', id2='groups_id', string='Auto Subscription',
|
||||
|
@ -79,7 +59,7 @@ class mail_group(osv.Model):
|
|||
'image': fields.binary("Photo",
|
||||
help="This field holds the image used as photo for the "\
|
||||
"user. The image is base64 encoded, and PIL-supported. "\
|
||||
"It is limited to a 12024x1024 px image."),
|
||||
"It is limited to a 1024x1024 px image."),
|
||||
'image_medium': fields.function(_get_image, fnct_inv=_set_image,
|
||||
string="Medium-sized photo", type="binary", multi="_get_image",
|
||||
store = {
|
||||
|
@ -96,17 +76,19 @@ class mail_group(osv.Model):
|
|||
help="Small-sized photo of the group. It is automatically "\
|
||||
"resized as a 50x50px image, with aspect ratio preserved. "\
|
||||
"Use this field anywhere a small image is required."),
|
||||
'last_month_msg_nbr': fields.function(_get_last_month_msg_nbr, type='integer',
|
||||
string='Messages count for last month'),
|
||||
'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="cascade",
|
||||
help="The email address associated with this group. New emails received will automatically "
|
||||
"create new topics."),
|
||||
'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="cascade", required=True,
|
||||
help="The email address associated with this group. New emails received will automatically "
|
||||
"create new topics."),
|
||||
}
|
||||
|
||||
def _get_default_employee_group(self, cr, uid, context=None):
|
||||
ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'group_user')
|
||||
return ref and ref[1] or False
|
||||
|
||||
def _get_default_image(self, cr, uid, context=None):
|
||||
image_path = openerp.modules.get_module_resource('mail', 'static/src/img', 'groupdefault.png')
|
||||
return tools.image_resize_image_big(open(image_path, 'rb').read().encode('base64'))
|
||||
|
||||
def _get_menu_parent(self, cr, uid, context=None):
|
||||
ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', 'mail_group_root')
|
||||
return ref and ref[1] or False
|
||||
|
@ -114,23 +96,17 @@ class mail_group(osv.Model):
|
|||
_defaults = {
|
||||
'public': 'groups',
|
||||
'group_public_id': _get_default_employee_group,
|
||||
'responsible_id': (lambda s, cr, uid, ctx: uid),
|
||||
'image': _get_default_image,
|
||||
'parent_id': _get_menu_parent,
|
||||
'alias_domain': False, # always hide alias during creation
|
||||
}
|
||||
|
||||
def _subscribe_user_with_group_m2m_command(self, cr, uid, ids, group_ids_command, context=None):
|
||||
# form: {'group_ids': [(3, 10), (3, 3), (4, 10), (4, 3)]} or {'group_ids': [(6, 0, [ids]}
|
||||
user_group_ids = [command[1] for command in group_ids_command if command[0] == 4]
|
||||
user_group_ids += [id for command in group_ids_command if command[0] == 6 for id in command[2]]
|
||||
# retrieve the user member of those groups
|
||||
user_ids = []
|
||||
res_groups_obj = self.pool.get('res.groups')
|
||||
for group in res_groups_obj.browse(cr, uid, user_group_ids, context=context):
|
||||
user_ids += [user.id for user in group.users]
|
||||
# subscribe the users
|
||||
return self.message_subscribe(cr, uid, ids, user_ids, context=context)
|
||||
def _subscribe_users(self, cr, uid, ids, context=None):
|
||||
for mail_group in self.browse(cr, uid, ids, context=context):
|
||||
partner_ids = []
|
||||
for group in mail_group.group_ids:
|
||||
partner_ids += [user.partner_id.id for user in group.users]
|
||||
self.message_subscribe(cr, uid, ids, partner_ids, context=context)
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
mail_alias = self.pool.get('mail.alias')
|
||||
|
@ -148,13 +124,13 @@ class mail_group(osv.Model):
|
|||
# Create client action for this group and link the menu to it
|
||||
ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', 'action_mail_group_feeds')
|
||||
if ref:
|
||||
search_ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', 'view_message_search_wall')
|
||||
search_ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mail', 'view_message_search')
|
||||
params = {
|
||||
'search_view_id': search_ref and search_ref[1] or False,
|
||||
'domain': [('model','=','mail.group'),('res_id','=',mail_group_id)],
|
||||
'res_model': 'mail.group',
|
||||
'res_id': mail_group_id,
|
||||
'thread_level': 2
|
||||
'domain': [('model','=','mail.group'), ('res_id','=',mail_group_id)],
|
||||
'context': {'default_model': 'mail.group', 'default_res_id': mail_group_id},
|
||||
'res_model': 'mail.message',
|
||||
'thread_level': 1,
|
||||
}
|
||||
cobj = self.pool.get('ir.actions.client')
|
||||
newref = cobj.copy(cr, uid, ref[1], default={'params': str(params), 'name': vals['name']}, context=context)
|
||||
|
@ -163,8 +139,7 @@ class mail_group(osv.Model):
|
|||
mail_alias.write(cr, uid, [vals['alias_id']], {"alias_force_thread_id": mail_group_id}, context)
|
||||
|
||||
if vals.get('group_ids'):
|
||||
self._subscribe_user_with_group_m2m_command(cr, uid, [mail_group_id], vals.get('group_ids'), context=context)
|
||||
|
||||
self._subscribe_users(cr, uid, [mail_group_id], context=context)
|
||||
return mail_group_id
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
|
@ -176,21 +151,17 @@ class mail_group(osv.Model):
|
|||
return res
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
result = super(mail_group, self).write(cr, uid, ids, vals, context=context)
|
||||
if vals.get('group_ids'):
|
||||
self._subscribe_user_with_group_m2m_command(cr, uid, ids, vals.get('group_ids'), context=context)
|
||||
return super(mail_group, self).write(cr, uid, ids, vals, context=context)
|
||||
self._subscribe_users(cr, uid, ids, vals.get('group_ids'), context=context)
|
||||
return result
|
||||
|
||||
def action_group_join(self, cr, uid, ids, context=None):
|
||||
return self.message_subscribe(cr, uid, ids, context=context)
|
||||
def action_follow(self, cr, uid, ids, context=None):
|
||||
""" Wrapper because message_subscribe_users take a user_ids=None
|
||||
that receive the context without the wrapper. """
|
||||
return self.message_subscribe_users(cr, uid, ids, context=context)
|
||||
|
||||
def action_group_leave(self, cr, uid, ids, context=None):
|
||||
return self.message_unsubscribe(cr, uid, ids, context=context)
|
||||
|
||||
# ----------------------------------------
|
||||
# OpenChatter methods and notifications
|
||||
# ----------------------------------------
|
||||
|
||||
def message_get_monitored_follower_fields(self, cr, uid, ids, context=None):
|
||||
""" Add 'responsible_id' to the monitored fields """
|
||||
res = super(mail_group, self).message_get_monitored_follower_fields(cr, uid, ids, context=context)
|
||||
return res + ['responsible_id']
|
||||
def action_unfollow(self, cr, uid, ids, context=None):
|
||||
""" Wrapper because message_unsubscribe_users take a user_ids=None
|
||||
that receive the context without the wrapper. """
|
||||
return self.message_unsubscribe_users(cr, uid, ids, context=context)
|
||||
|
|
|
@ -40,11 +40,12 @@ class ir_ui_menu(osv.osv):
|
|||
""" Override to take off menu entries (mail.group) the user is not
|
||||
following. """
|
||||
ids = super(ir_ui_menu, self).search(cr, uid, args, offset=0, limit=None, order=order, context=context, count=False)
|
||||
partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
|
||||
follower_obj = self.pool.get('mail.followers')
|
||||
for menu in self.browse(cr, uid, ids, context=context):
|
||||
if menu.mail_group_id:
|
||||
sub_ids = follower_obj.search(cr, uid, [
|
||||
('user_id', '=', uid), ('res_model', '=', 'mail.group'),
|
||||
('partner_id', '=', partner_id), ('res_model', '=', 'mail.group'),
|
||||
('res_id', '=', menu.mail_group_id.id)
|
||||
], context=context)
|
||||
if not sub_ids:
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<!-- this record will be dynamically adapted to each new menu created -->
|
||||
<record id="action_mail_group_feeds" model="ir.actions.client">
|
||||
<field name="name">Discussion Group</field>
|
||||
<field name="tag">mail.wall</field>
|
||||
<field name="params" eval="{'search_view_id': ref('view_message_search_wall')}"/>
|
||||
<field name="res_model">mail.message</field>
|
||||
</record>
|
||||
|
||||
<!-- Group Kanban View !-->
|
||||
|
@ -14,8 +15,9 @@
|
|||
<field name="priority" eval="10"/>
|
||||
<field name="arch" type="xml">
|
||||
<kanban>
|
||||
<field name="message_follower_ids"/>
|
||||
<field name="message_is_follower"/>
|
||||
<field name="message_follower_ids"/>
|
||||
<field name="message_summary"/>
|
||||
<templates>
|
||||
<t t-name="kanban-description">
|
||||
<div class="oe_group_description" t-if="record.description.raw_value">
|
||||
|
@ -30,13 +32,9 @@
|
|||
<div class="oe_group_details">
|
||||
<h4><a type="open"><field name="name"/></a></h4>
|
||||
<ul>
|
||||
<!-- <li><field name="message_follower_count"/> member(s)</li> -->
|
||||
<li>
|
||||
<t t-raw="record.message_follower_ids.raw_value.length"/> member(s)
|
||||
</li>
|
||||
<li t-if="! record.message_is_follower.raw_value"><a name="action_group_join" string="Join" type="object" class="oe_group_join">Not following</a></li>
|
||||
<li t-if="record.message_is_follower.raw_value"><a name="action_group_leave" string="Join" type="object" class="oe_group_leave">Following</a></li>
|
||||
<li><field name="last_month_msg_nbr"/> messages last month</li>
|
||||
<li><t t-raw="record.message_summary.raw_value"/></li>
|
||||
<li t-if="! record.message_is_follower.raw_value"><a name="action_follow" string="Join" type="object" class="oe_group_join">Not following</a></li>
|
||||
<li t-if="record.message_is_follower.raw_value"><a name="action_unfollow" string="Leave" type="object" class="oe_group_leave">Following</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -80,16 +78,12 @@
|
|||
/>
|
||||
<field name="group_ids" widget="many2many_tags" class="oe_inline"/>
|
||||
</group>
|
||||
<group class="oe_edit_only">
|
||||
<field name="responsible_id" class="oe_inline"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids" widget="mail_thread"
|
||||
options='{"thread_level": 1}'/>
|
||||
<field name="message_follower_ids" widget="mail_followers"
|
||||
context="{'lapin': 'nouille'}" image="image_small"/>
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
|
@ -103,7 +97,6 @@
|
|||
<field name="arch" type="xml">
|
||||
<tree string="Groups">
|
||||
<field name="name" string="Group Name"/>
|
||||
<field name="responsible_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
@ -116,7 +109,6 @@
|
|||
<field name="arch" type="xml">
|
||||
<search string="Search Groups">
|
||||
<field name="name" string="Group"/>
|
||||
<field name="responsible_id"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
|
|
@ -0,0 +1,204 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2010-today OpenERP SA (<http://www.openerp.com>)
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# import ast
|
||||
import base64
|
||||
import logging
|
||||
import tools
|
||||
|
||||
from osv import osv
|
||||
from osv import fields
|
||||
from tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class mail_mail(osv.Model):
|
||||
""" Model holding RFC2822 email messages to send. This model also provides
|
||||
facilities to queue and send new email messages. """
|
||||
_name = 'mail.mail'
|
||||
_description = 'Outgoing Mails'
|
||||
_inherits = {'mail.message': 'mail_message_id'}
|
||||
_order = 'id desc'
|
||||
|
||||
_columns = {
|
||||
'mail_message_id': fields.many2one('mail.message', 'Message', required=True, ondelete='cascade'),
|
||||
'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing mail server', readonly=1),
|
||||
'state': fields.selection([
|
||||
('outgoing', 'Outgoing'),
|
||||
('sent', 'Sent'),
|
||||
('received', 'Received'),
|
||||
('exception', 'Delivery Failed'),
|
||||
('cancel', 'Cancelled'),
|
||||
], 'Status', readonly=True),
|
||||
'auto_delete': fields.boolean('Auto Delete',
|
||||
help="Permanently delete this email after sending it, to save space"),
|
||||
'references': fields.text('References', help='Message references, such as identifiers of previous messages', readonly=1),
|
||||
'email_from': fields.char('From', help='Message sender, taken from user preferences.'),
|
||||
'email_to': fields.text('To', help='Message recipients'),
|
||||
'email_cc': fields.char('Cc', help='Carbon copy message recipients'),
|
||||
'reply_to': fields.char('Reply-To', help='Preferred response address for the message'),
|
||||
'body_html': fields.text('Rich-text Contents', help="Rich-text/HTML message"),
|
||||
|
||||
# Auto-detected based on create() - if 'mail_message_id' was passed then this mail is a notification
|
||||
# and during unlink() we will cascade delete the parent and its attachments
|
||||
'notification': fields.boolean('Is Notification')
|
||||
}
|
||||
|
||||
def _get_default_from(self, cr, uid, context=None):
|
||||
cur = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
if not cur.alias_domain:
|
||||
raise osv.except_osv(_('Invalid Action!'), _('Unable to send email, set an alias domain in your server settings.'))
|
||||
return cur.alias_name + '@' + cur.alias_domain
|
||||
|
||||
_defaults = {
|
||||
'state': 'outgoing',
|
||||
'email_from': lambda self, cr, uid, ctx=None: self._get_default_from(cr, uid, ctx),
|
||||
}
|
||||
|
||||
def create(self, cr, uid, values, context=None):
|
||||
if 'notification' not in values and values.get('mail_message_id'):
|
||||
values['notification'] = True
|
||||
return super(mail_mail,self).create(cr, uid, values, context=context)
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
# cascade-delete the parent message for all mails that are not created for a notification
|
||||
ids_to_cascade = self.search(cr, uid, [('notification','=',False),('id','in',ids)])
|
||||
parent_msg_ids = [m.mail_message_id.id for m in self.browse(cr, uid, ids_to_cascade, context=context)]
|
||||
res = super(mail_mail,self).unlink(cr, uid, ids, context=context)
|
||||
self.pool.get('mail.message').unlink(cr, uid, parent_msg_ids, context=context)
|
||||
return res
|
||||
|
||||
def mark_outgoing(self, cr, uid, ids, context=None):
|
||||
return self.write(cr, uid, ids, {'state':'outgoing'}, context=context)
|
||||
|
||||
def cancel(self, cr, uid, ids, context=None):
|
||||
return self.write(cr, uid, ids, {'state':'cancel'}, context=context)
|
||||
|
||||
def process_email_queue(self, cr, uid, ids=None, context=None):
|
||||
"""Send immediately queued messages, committing after each
|
||||
message is sent - this is not transactional and should
|
||||
not be called during another transaction!
|
||||
|
||||
:param list ids: optional list of emails ids to send. If passed
|
||||
no search is performed, and these ids are used
|
||||
instead.
|
||||
:param dict context: if a 'filters' key is present in context,
|
||||
this value will be used as an additional
|
||||
filter to further restrict the outgoing
|
||||
messages to send (by default all 'outgoing'
|
||||
messages are sent).
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
if not ids:
|
||||
filters = ['&', ('state', '=', 'outgoing'), ('type', '=', 'email')]
|
||||
if 'filters' in context:
|
||||
filters.extend(context['filters'])
|
||||
ids = self.search(cr, uid, filters, context=context)
|
||||
res = None
|
||||
try:
|
||||
# Force auto-commit - this is meant to be called by
|
||||
# the scheduler, and we can't allow rolling back the status
|
||||
# of previously sent emails!
|
||||
res = self.send(cr, uid, ids, auto_commit=True, context=context)
|
||||
except Exception:
|
||||
_logger.exception("Failed processing mail queue")
|
||||
return res
|
||||
|
||||
def _postprocess_sent_message(self, cr, uid, mail, context=None):
|
||||
"""Perform any post-processing necessary after sending ``mail``
|
||||
successfully, including deleting it completely along with its
|
||||
attachment if the ``auto_delete`` flag of the mail was set.
|
||||
Overridden by subclasses for extra post-processing behaviors.
|
||||
|
||||
:param browse_record mail: the mail that was just sent
|
||||
:return: True
|
||||
"""
|
||||
if mail.auto_delete:
|
||||
mail.unlink()
|
||||
return True
|
||||
|
||||
def _send_get_mail_subject(self, cr, uid, mail, force=False, context=None):
|
||||
""" if void and related document: '<Author> posted on <Resource>'
|
||||
:param mail: mail.mail browse_record """
|
||||
if force or (not mail.subject and mail.model and mail.res_id):
|
||||
return '%s posted on %s' % (mail.author_id.name, mail.record_name)
|
||||
return mail.subject
|
||||
|
||||
def send(self, cr, uid, ids, auto_commit=False, context=None):
|
||||
""" Sends the selected emails immediately, ignoring their current
|
||||
state (mails that have already been sent should not be passed
|
||||
unless they should actually be re-sent).
|
||||
Emails successfully delivered are marked as 'sent', and those
|
||||
that fail to be deliver are marked as 'exception', and the
|
||||
corresponding error mail is output in the server logs.
|
||||
|
||||
:param bool auto_commit: whether to force a commit of the mail status
|
||||
after sending each mail (meant only for scheduler processing);
|
||||
should never be True during normal transactions (default: False)
|
||||
:return: True
|
||||
"""
|
||||
ir_mail_server = self.pool.get('ir.mail_server')
|
||||
for mail in self.browse(cr, uid, ids, context=context):
|
||||
try:
|
||||
body = mail.body_html
|
||||
subject = self._send_get_mail_subject(cr, uid, mail, context=context)
|
||||
|
||||
# handle attachments
|
||||
attachments = []
|
||||
for attach in mail.attachment_ids:
|
||||
attachments.append((attach.datas_fname, base64.b64decode(attach.datas)))
|
||||
|
||||
# use only sanitized html and set its plaintexted version as alternative
|
||||
body_alternative = tools.html2plaintext(body)
|
||||
content_subtype_alternative = 'plain'
|
||||
|
||||
# build an RFC2822 email.message.Message object and send it without queuing
|
||||
msg = ir_mail_server.build_email(
|
||||
email_from = mail.email_from,
|
||||
email_to = tools.email_split(mail.email_to),
|
||||
subject = subject,
|
||||
body = body,
|
||||
body_alternative = body_alternative,
|
||||
email_cc = tools.email_split(mail.email_cc),
|
||||
reply_to = mail.reply_to,
|
||||
attachments = attachments,
|
||||
message_id = mail.message_id,
|
||||
references = mail.references,
|
||||
object_id = mail.res_id and ('%s-%s' % (mail.res_id, mail.model)),
|
||||
subtype = 'html',
|
||||
subtype_alternative = content_subtype_alternative)
|
||||
res = ir_mail_server.send_email(cr, uid, msg,
|
||||
mail_server_id=mail.mail_server_id.id, context=context)
|
||||
if res:
|
||||
mail.write({'state':'sent', 'message_id': res})
|
||||
else:
|
||||
mail.write({'state':'exception'})
|
||||
mail.refresh()
|
||||
if mail.state == 'sent':
|
||||
self._postprocess_sent_message(cr, uid, mail, context=context)
|
||||
except Exception:
|
||||
_logger.exception('failed sending mail.mail %s', mail.id)
|
||||
mail.write({'state':'exception'})
|
||||
|
||||
if auto_commit == True:
|
||||
cr.commit()
|
||||
return True
|
|
@ -0,0 +1,127 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="view_mail_form">
|
||||
<field name="name">mail.mail.form</field>
|
||||
<field name="model">mail.mail</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Email message" version="7.0">
|
||||
<sheet>
|
||||
<label for="subject" class="oe_edit_only"/>
|
||||
<h2><field name="subject"/></h2>
|
||||
<div>
|
||||
by <field name="author_id" class="oe_inline" string="User"/> on <field name="date" class="oe_inline"/>
|
||||
<button name="%(action_email_compose_message_wizard)d" string="Reply" type="action" icon="terp-mail-replied"
|
||||
context="{'default_composition_mode':'reply', 'default_parent_id': active_id}" states='received,sent,exception,cancel'/>
|
||||
</div>
|
||||
<notebook colspan="4">
|
||||
<page string="Message Details">
|
||||
<group>
|
||||
<group>
|
||||
<field name="email_from"/>
|
||||
<field name="email_to"/>
|
||||
<field name="email_cc"/>
|
||||
<field name="reply_to"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="partner_ids" widget="many2many_tags"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Body">
|
||||
<field name="body_html"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</page>
|
||||
<page string="Advanced" groups="base.group_no_one">
|
||||
<group>
|
||||
<group>
|
||||
<field name="auto_delete"/>
|
||||
<field name="type"/>
|
||||
<field name="state" colspan="2"/>
|
||||
<field name="mail_server_id"/>
|
||||
<field name="model"/>
|
||||
<field name="res_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="message_id"/>
|
||||
<field name="references"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Attachments">
|
||||
<field name="attachment_ids"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_tree">
|
||||
<field name="name">mail.mail.tree</field>
|
||||
<field name="model">mail.mail</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Emails" colors="grey:state in ('sent', 'cancel');blue:state=='outgoing';red:state=='exception';black:state=='received'">
|
||||
<field name="date"/>
|
||||
<field name="subject"/>
|
||||
<field name="author_id" string="User"/>
|
||||
<field name="message_id" invisible="1"/>
|
||||
<field name="partner_ids" invisible="1"/>
|
||||
<field name="model" invisible="1"/>
|
||||
<field name="res_id" invisible="1"/>
|
||||
<field name="email_from" invisible="1"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="type" invisible="1"/>
|
||||
<button name="send" string="Send Now" type="object" icon="gtk-media-play" states='outgoing'/>
|
||||
<button name="mark_outgoing" string="Retry" type="object" icon="gtk-redo" states='exception,cancel'/>
|
||||
<button name="cancel" string="Cancel" type="object" icon="terp-gtk-stop" states='outgoing'/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_search">
|
||||
<field name="name">mail.mail.search</field>
|
||||
<field name="model">mail.mail</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Email Search">
|
||||
<field name="email_from" filter_domain="['|' '|',('email_from','ilike',self), ('email_to','ilike',self), ('subject','ilike',self)]" string="Email"/>
|
||||
<field name="date"/>
|
||||
<filter icon="terp-camera_test" name="received" string="Received" domain="[('state','=','received')]"/>
|
||||
<filter icon="terp-call-start" name="outgoing" string="Outgoing" domain="[('state','=','outgoing')]"/>
|
||||
<filter icon="terp-check" name="sent" string="Sent" domain="[('state','=','sent')]"/>
|
||||
<filter icon="terp-gtk-stop" name="exception" string="Failed" domain="[('state','=','exception')]"/>
|
||||
<separator/>
|
||||
<filter icon="terp-camera_test" name="type_email" string="Email" domain="[('type','=','email')]"/>
|
||||
<filter icon="terp-camera_test" name="type_comment" string="Comment" domain="[('type','=','comment')]"/>
|
||||
<filter icon="terp-camera_test" name="type_notification" string="Notification" domain="[('type','=','notification')]"/>
|
||||
<group expand="0" string="Extended Filters...">
|
||||
<field name="author_id"/>
|
||||
<field name="partner_ids"/>
|
||||
<field name="model"/>
|
||||
<field name="res_id"/>
|
||||
</group>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Status" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}"/>
|
||||
<filter string="Partner" icon="terp-partner" domain="[]" context="{'group_by':'partner_id'}"/>
|
||||
<filter string="User" name="User" icon="terp-personal" context="{'group_by':'user_id'}"/>
|
||||
<filter string="Thread" icon="terp-mail-" domain="[]" context="{'group_by':'message_id'}"/>
|
||||
<filter string="Month" help="Creation Month" icon="terp-go-month" domain="[]" context="{'group_by':'date'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_mail_mail" model="ir.actions.act_window">
|
||||
<field name="name">Emails</field>
|
||||
<field name="res_model">mail.mail</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{'search_default_outgoing': 1, 'search_default_type_email': 1}</field>
|
||||
<field name="search_view_id" ref="view_mail_search"/>
|
||||
</record>
|
||||
|
||||
<!-- Add menu entry in Settings/Email -->
|
||||
<menuitem name="Emails" id="menu_mail_mail" parent="base.menu_email" action="action_view_mail_mail" />
|
||||
</data>
|
||||
</openerp>
|
|
@ -19,25 +19,13 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import ast
|
||||
import base64
|
||||
import dateutil.parser
|
||||
import email
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
import datetime
|
||||
from email.header import decode_header
|
||||
from email.message import Message
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from osv import osv
|
||||
from osv import fields
|
||||
import pytz
|
||||
from tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from tools.translate import _
|
||||
import tools
|
||||
|
||||
from email.header import decode_header
|
||||
from operator import itemgetter
|
||||
from osv import osv, fields
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
""" Some tools for parsing / creating email fields """
|
||||
|
@ -47,200 +35,222 @@ def decode(text):
|
|||
text = decode_header(text.replace('\r', ''))
|
||||
return ''.join([tools.ustr(x[0], x[1]) for x in text])
|
||||
|
||||
def mail_tools_to_email(text):
|
||||
"""Return a list of the email addresses found in ``text``"""
|
||||
if not text: return []
|
||||
return re.findall(r'([^ ,<@]+@[^> ,]+)', text)
|
||||
class mail_message(osv.Model):
|
||||
""" Messages model: system notification (replacing res.log notifications),
|
||||
comments (OpenChatter discussion) and incoming emails. """
|
||||
_name = 'mail.message'
|
||||
_description = 'Message'
|
||||
_inherit = ['ir.needaction_mixin']
|
||||
_order = 'id desc'
|
||||
|
||||
# TODO: remove that after cleaning
|
||||
def to_email(text):
|
||||
return mail_tools_to_email(text)
|
||||
_message_read_limit = 10
|
||||
_message_record_name_length = 18
|
||||
|
||||
class mail_message_common(osv.TransientModel):
|
||||
""" Common abstract class for holding the main attributes of a
|
||||
message object. It could be reused as parent model for any
|
||||
database model or wizard screen that needs to hold a kind of
|
||||
message.
|
||||
All internal logic should be in another model while this
|
||||
model holds the basics of a message. For example, a wizard for writing
|
||||
emails should inherit from this class and not from mail.message."""
|
||||
def _shorten_name(self, name):
|
||||
if len(name) <= (self._message_record_name_length + 3):
|
||||
return name
|
||||
return name[:self._message_record_name_length] + '...'
|
||||
|
||||
def get_body(self, cr, uid, ids, name, arg, context=None):
|
||||
""" get correct body version: body_html for html messages, and
|
||||
body_text for plain text messages
|
||||
"""
|
||||
result = dict.fromkeys(ids, '')
|
||||
for message in self.browse(cr, uid, ids, context=context):
|
||||
if message.content_subtype == 'html':
|
||||
result[message.id] = message.body_html
|
||||
else:
|
||||
result[message.id] = message.body_text
|
||||
return result
|
||||
|
||||
def search_body(self, cr, uid, obj, name, args, context=None):
|
||||
# will receive:
|
||||
# - obj: mail.message object
|
||||
# - name: 'body'
|
||||
# - args: [('body', 'ilike', 'blah')]
|
||||
return ['|', '&', ('content_subtype', '=', 'html'), ('body_html', args[0][1], args[0][2]), ('body_text', args[0][1], args[0][2])]
|
||||
|
||||
def get_record_name(self, cr, uid, ids, name, arg, context=None):
|
||||
def _get_record_name(self, cr, uid, ids, name, arg, context=None):
|
||||
""" Return the related document name, using get_name. """
|
||||
result = dict.fromkeys(ids, '')
|
||||
for message in self.browse(cr, uid, ids, context=context):
|
||||
if not message.model or not message.res_id:
|
||||
continue
|
||||
result[message.id] = self.pool.get(message.model).name_get(cr, uid, [message.res_id], context=context)[0][1]
|
||||
result[message.id] = self._shorten_name(self.pool.get(message.model).name_get(cr, uid, [message.res_id], context=context)[0][1])
|
||||
return result
|
||||
|
||||
def _get_unread(self, cr, uid, ids, name, arg, context=None):
|
||||
""" Compute if the message is unread by the current user. """
|
||||
res = dict((id, {'unread': False}) for id in ids)
|
||||
partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
|
||||
notif_obj = self.pool.get('mail.notification')
|
||||
notif_ids = notif_obj.search(cr, uid, [
|
||||
('partner_id', 'in', [partner_id]),
|
||||
('message_id', 'in', ids),
|
||||
('read', '=', False)
|
||||
], context=context)
|
||||
for notif in notif_obj.browse(cr, uid, notif_ids, context=context):
|
||||
res[notif.message_id.id]['unread'] = True
|
||||
return res
|
||||
|
||||
def _search_unread(self, cr, uid, obj, name, domain, context=None):
|
||||
""" Search for messages unread by the current user. Condition is
|
||||
inversed because we search unread message on a read column. """
|
||||
if domain[0][2]:
|
||||
read_cond = '(read = false or read is null)'
|
||||
else:
|
||||
read_cond = 'read = true'
|
||||
partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
|
||||
cr.execute("SELECT message_id FROM mail_notification "\
|
||||
"WHERE partner_id = %%s AND %s" % read_cond,
|
||||
(partner_id,))
|
||||
return [('id', 'in', [r[0] for r in cr.fetchall()])]
|
||||
|
||||
def name_get(self, cr, uid, ids, context=None):
|
||||
# name_get may receive int id instead of an id list
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
res = []
|
||||
for message in self.browse(cr, uid, ids, context=context):
|
||||
name = ''
|
||||
if message.subject:
|
||||
name = '%s: ' % (message.subject)
|
||||
if message.body_text:
|
||||
name = '%s%s ' % (name, message.body_text[0:20])
|
||||
if message.date:
|
||||
name = '%s(%s)' % (name, message.date)
|
||||
res.append((message.id, name))
|
||||
name = '%s: %s' % (message.subject or '', message.body or '')
|
||||
res.append((message.id, self._shorten_name(name.lstrip(' :'))))
|
||||
return res
|
||||
|
||||
_name = 'mail.message.common'
|
||||
_rec_name = 'subject'
|
||||
_columns = {
|
||||
'subject': fields.char('Subject', size=512),
|
||||
'model': fields.char('Related Document Model', size=128, select=1),
|
||||
'res_id': fields.integer('Related Document ID', select=1),
|
||||
'record_name': fields.function(get_record_name, type='string',
|
||||
string='Message Record Name',
|
||||
help="Name get of the related document."),
|
||||
'date': fields.datetime('Date'),
|
||||
'email_from': fields.char('From', size=128, help='Message sender, taken from user preferences.'),
|
||||
'email_to': fields.char('To', size=256, help='Message recipients'),
|
||||
'email_cc': fields.char('Cc', size=256, help='Carbon copy message recipients'),
|
||||
'email_bcc': fields.char('Bcc', size=256, help='Blind carbon copy message recipients'),
|
||||
'reply_to':fields.char('Reply-To', size=256, help='Preferred response address for the message'),
|
||||
'headers': fields.text('Message Headers', readonly=1,
|
||||
help="Full message headers, e.g. SMTP session headers (usually available on inbound messages only)"),
|
||||
'message_id': fields.char('Message-Id', size=256, help='Message unique identifier', select=1, readonly=1),
|
||||
'references': fields.text('References', help='Message references, such as identifiers of previous messages', readonly=1),
|
||||
'content_subtype': fields.char('Message content subtype', size=32,
|
||||
oldname="subtype", readonly=1,
|
||||
help="Type of message, usually 'html' or 'plain', used to select "\
|
||||
"plain-text or rich-text contents accordingly"),
|
||||
'body_text': fields.text('Text Contents', help="Plain-text version of the message"),
|
||||
'body_html': fields.html('Rich-text Contents', help="Rich-text/HTML version of the message"),
|
||||
'body': fields.function(get_body, fnct_search = search_body, type='text',
|
||||
string='Message Content', store=True,
|
||||
help="Content of the message. This content equals the body_text field "\
|
||||
"for plain-test messages, and body_html for rich-text/HTML "\
|
||||
"messages. This allows having one field if we want to access "\
|
||||
"the content matching the message content_subtype."),
|
||||
'parent_id': fields.many2one('mail.message.common', 'Parent Message',
|
||||
select=True, ondelete='set null',
|
||||
help="Parent message, used for displaying as threads with hierarchy"),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'content_subtype': 'plain',
|
||||
'date': (lambda *a: fields.datetime.now()),
|
||||
}
|
||||
|
||||
class mail_message(osv.Model):
|
||||
"""Model holding messages: system notification (replacing res.log
|
||||
notifications), comments (for OpenChatter feature) and
|
||||
RFC2822 email messages. This model also provides facilities to
|
||||
parse, queue and send new email messages. Type of messages
|
||||
are differentiated using the 'type' column. """
|
||||
|
||||
_name = 'mail.message'
|
||||
_inherit = 'mail.message.common'
|
||||
_description = 'Mail Message (email, comment, notification)'
|
||||
_order = 'date desc'
|
||||
|
||||
def open_document(self, cr, uid, ids, context=None):
|
||||
""" Open the message related document. Note that only the document of
|
||||
ids[0] will be opened.
|
||||
TODO: how to determine the action to use ?
|
||||
"""
|
||||
action_data = False
|
||||
if not ids:
|
||||
return action_data
|
||||
msg = self.browse(cr, uid, ids[0], context=context)
|
||||
ir_act_window = self.pool.get('ir.actions.act_window')
|
||||
action_ids = ir_act_window.search(cr, uid, [('res_model', '=', msg.model)], context=context)
|
||||
if action_ids:
|
||||
action_data = ir_act_window.read(cr, uid, action_ids[0], context=context)
|
||||
action_data.update({
|
||||
'domain' : "[('id', '=', %d)]" % (msg.res_id),
|
||||
'nodestroy': True,
|
||||
'context': {}
|
||||
})
|
||||
return action_data
|
||||
|
||||
def open_attachment(self, cr, uid, ids, context=None):
|
||||
""" Open the message related attachments.
|
||||
TODO: how to determine the action to use ?
|
||||
"""
|
||||
action_data = False
|
||||
if not ids:
|
||||
return action_data
|
||||
action_pool = self.pool.get('ir.actions.act_window')
|
||||
messages = self.browse(cr, uid, ids, context=context)
|
||||
att_ids = [x.id for message in messages for x in message.attachment_ids]
|
||||
action_ids = action_pool.search(cr, uid, [('res_model', '=', 'ir.attachment')], context=context)
|
||||
if action_ids:
|
||||
action_data = action_pool.read(cr, uid, action_ids[0], context=context)
|
||||
action_data.update({
|
||||
'domain': [('id', 'in', att_ids)],
|
||||
'nodestroy': True
|
||||
})
|
||||
return action_data
|
||||
|
||||
_columns = {
|
||||
'type': fields.selection([
|
||||
('email', 'email'),
|
||||
('email', 'Email'),
|
||||
('comment', 'Comment'),
|
||||
('notification', 'System notification'),
|
||||
], 'Type',
|
||||
help="Message type: email for email message, notification for system "\
|
||||
"message, comment for other messages such as user replies"),
|
||||
'partner_id': fields.many2one('res.partner', 'Related partner',
|
||||
help="Deprecated field. Use partner_ids instead."),
|
||||
'partner_ids': fields.many2many('res.partner',
|
||||
'mail_message_res_partner_rel',
|
||||
'message_id', 'partner_id', 'Destination partners',
|
||||
help="When sending emails through the social network composition wizard"\
|
||||
"you may choose to send a copy of the mail to partners."),
|
||||
'user_id': fields.many2one('res.users', 'Related User', readonly=1),
|
||||
"message, comment for other messages such as user replies"),
|
||||
'author_id': fields.many2one('res.partner', 'Author', required=True),
|
||||
'partner_ids': fields.many2many('res.partner', 'mail_notification', 'message_id', 'partner_id', 'Recipients'),
|
||||
'attachment_ids': fields.many2many('ir.attachment', 'message_attachment_rel',
|
||||
'message_id', 'attachment_id', 'Attachments'),
|
||||
'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing mail server', readonly=1),
|
||||
'state': fields.selection([
|
||||
('outgoing', 'Outgoing'),
|
||||
('sent', 'Sent'),
|
||||
('received', 'Received'),
|
||||
('exception', 'Delivery Failed'),
|
||||
('cancel', 'Cancelled'),
|
||||
], 'Status', readonly=True),
|
||||
'auto_delete': fields.boolean('Auto Delete',
|
||||
help="Permanently delete this email after sending it, to save space"),
|
||||
'original': fields.binary('Original', readonly=1,
|
||||
help="Original version of the message, as it was sent on the network"),
|
||||
'parent_id': fields.many2one('mail.message', 'Parent Message',
|
||||
select=True, ondelete='set null',
|
||||
help="Parent message, used for displaying as threads with hierarchy"),
|
||||
'parent_id': fields.many2one('mail.message', 'Parent Message', select=True, ondelete='set null', help="Initial thread message."),
|
||||
'child_ids': fields.one2many('mail.message', 'parent_id', 'Child Messages'),
|
||||
'model': fields.char('Related Document Model', size=128, select=1),
|
||||
'res_id': fields.integer('Related Document ID', select=1),
|
||||
'record_name': fields.function(_get_record_name, type='string',
|
||||
string='Message Record Name',
|
||||
help="Name get of the related document."),
|
||||
'notification_ids': fields.one2many('mail.notification', 'message_id', 'Notifications'),
|
||||
'subject': fields.char('Subject'),
|
||||
'date': fields.datetime('Date'),
|
||||
'message_id': fields.char('Message-Id', help='Message unique identifier', select=1, readonly=1),
|
||||
'body': fields.html('Contents', help='Automatically sanitized HTML contents'),
|
||||
'unread': fields.function(_get_unread, fnct_search=_search_unread,
|
||||
type='boolean', string='Unread',
|
||||
help='Functional field to search for unread messages linked to uid'),
|
||||
}
|
||||
|
||||
def _needaction_domain_get(self, cr, uid, context=None):
|
||||
if self._needaction:
|
||||
return [('unread', '=', True)]
|
||||
return []
|
||||
|
||||
def _get_default_author(self, cr, uid, context=None):
|
||||
return self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
|
||||
|
||||
_defaults = {
|
||||
'type': 'email',
|
||||
'state': 'received',
|
||||
'date': lambda *a: fields.datetime.now(),
|
||||
'author_id': lambda self, cr, uid, ctx={}: self._get_default_author(cr, uid, ctx),
|
||||
'body': '',
|
||||
}
|
||||
|
||||
#------------------------------------------------------
|
||||
# Message loading for web interface
|
||||
#------------------------------------------------------
|
||||
|
||||
def _message_dict_get(self, cr, uid, msg, context=None):
|
||||
""" Return a dict representation of the message browse record. """
|
||||
attachment_ids = self.pool.get('ir.attachment').name_get(cr, uid, [x.id for x in msg.attachment_ids], context=context)
|
||||
author_id = self.pool.get('res.partner').name_get(cr, uid, [msg.author_id.id], context=context)[0]
|
||||
author_user_id = self.pool.get('res.users').name_get(cr, uid, [msg.author_id.user_ids[0].id], context=context)[0]
|
||||
partner_ids = self.pool.get('res.partner').name_get(cr, uid, [x.id for x in msg.partner_ids], context=context)
|
||||
return {
|
||||
'id': msg.id,
|
||||
'type': msg.type,
|
||||
'attachment_ids': attachment_ids,
|
||||
'body': msg.body,
|
||||
'model': msg.model,
|
||||
'res_id': msg.res_id,
|
||||
'record_name': msg.record_name,
|
||||
'subject': msg.subject,
|
||||
'date': msg.date,
|
||||
'author_id': author_id,
|
||||
'author_user_id': author_user_id,
|
||||
'partner_ids': partner_ids,
|
||||
'child_ids': [],
|
||||
}
|
||||
|
||||
def message_read_tree_flatten(self, cr, uid, messages, current_level, level, context=None):
|
||||
""" Given a tree with several roots of following structure :
|
||||
[ {'id': 1, 'child_ids': [
|
||||
{'id': 11, 'child_ids': [...] },],
|
||||
{...} ]
|
||||
Flatten it to have a maximum number of levels, 0 being flat and
|
||||
sort messages in a level according to a key of the messages.
|
||||
Perform the flattening at leafs if above the maximum depth, then get
|
||||
back in the tree.
|
||||
:param context: ``sort_key``: key for sorting (id by default)
|
||||
:param context: ``sort_reverse``: reverser order for sorting (True by default)
|
||||
"""
|
||||
def _flatten(msg_dict):
|
||||
""" from {'id': x, 'child_ids': [{child1}, {child2}]}
|
||||
get [{'id': x, 'child_ids': []}, {child1}, {child2}]
|
||||
"""
|
||||
child_ids = msg_dict.pop('child_ids', [])
|
||||
msg_dict['child_ids'] = []
|
||||
return [msg_dict] + child_ids
|
||||
# return sorted([msg_dict] + child_ids, key=itemgetter('id'), reverse=True)
|
||||
context = context or {}
|
||||
# Depth-first flattening
|
||||
for message in messages:
|
||||
if message.get('type') == 'expandable':
|
||||
continue
|
||||
message['child_ids'] = self.message_read_tree_flatten(cr, uid, message['child_ids'], current_level + 1, level, context=context)
|
||||
# Flatten if above maximum depth
|
||||
if current_level < level:
|
||||
return_list = messages
|
||||
else:
|
||||
return_list = []
|
||||
for message in messages:
|
||||
for flat_message in _flatten(message):
|
||||
return_list.append(flat_message)
|
||||
return sorted(return_list, key=itemgetter(context.get('sort_key', 'id')), reverse=context.get('sort_reverse', True))
|
||||
|
||||
def message_read(self, cr, uid, ids=False, domain=[], thread_level=0, limit=None, context=None):
|
||||
""" If IDs are provided, fetch these records. Otherwise use the domain
|
||||
to fetch the matching records.
|
||||
After having fetched the records provided by IDs, it will fetch the
|
||||
parents to have well-formed threads.
|
||||
:return list: list of trees of messages
|
||||
"""
|
||||
limit = limit or self._message_read_limit
|
||||
context = context or {}
|
||||
if not ids:
|
||||
ids = self.search(cr, uid, domain, context=context, limit=limit)
|
||||
messages = self.browse(cr, uid, ids, context=context)
|
||||
|
||||
result = []
|
||||
tree = {} # key: ID, value: record
|
||||
for msg in messages:
|
||||
if len(result) < (limit - 1):
|
||||
record = self._message_dict_get(cr, uid, msg, context=context)
|
||||
if thread_level and msg.parent_id:
|
||||
while msg.parent_id:
|
||||
if msg.parent_id.id in tree:
|
||||
record_parent = tree[msg.parent_id.id]
|
||||
else:
|
||||
record_parent = self._message_dict_get(cr, uid, msg.parent_id, context=context)
|
||||
if msg.parent_id.parent_id:
|
||||
tree[msg.parent_id.id] = record_parent
|
||||
if record['id'] not in [x['id'] for x in record_parent['child_ids']]:
|
||||
record_parent['child_ids'].append(record)
|
||||
record = record_parent
|
||||
msg = msg.parent_id
|
||||
if msg.id not in tree:
|
||||
result.append(record)
|
||||
tree[msg.id] = record
|
||||
else:
|
||||
result.append({
|
||||
'type': 'expandable',
|
||||
'domain': [('id', '<=', msg.id)] + domain,
|
||||
'context': context,
|
||||
'thread_level': thread_level, # should be improve accodting to level of records
|
||||
'id': -1,
|
||||
})
|
||||
break
|
||||
|
||||
# Flatten the result
|
||||
if thread_level > 0:
|
||||
result = self.message_read_tree_flatten(cr, uid, result, 0, thread_level, context=context)
|
||||
return result
|
||||
|
||||
#------------------------------------------------------
|
||||
# Email api
|
||||
#------------------------------------------------------
|
||||
|
@ -250,430 +260,84 @@ class mail_message(osv.Model):
|
|||
if not cr.fetchone():
|
||||
cr.execute("""CREATE INDEX mail_message_model_res_id_idx ON mail_message (model, res_id)""")
|
||||
|
||||
def check(self, cr, uid, ids, mode, context=None, values=None):
|
||||
"""Restricts the access to a mail.message, according to referred model
|
||||
"""
|
||||
if not ids:
|
||||
return
|
||||
res_ids = {}
|
||||
def check_access_rule(self, cr, uid, ids, operation, context=None):
|
||||
""" mail.message access rule check
|
||||
- message received (a notification exists) -> ok
|
||||
- check rules of related document if exists
|
||||
- fallback on normal mail.message check """
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
cr.execute('SELECT DISTINCT model, res_id FROM mail_message WHERE id = ANY (%s)', (ids,))
|
||||
for rmod, rid in cr.fetchall():
|
||||
|
||||
# check messages for which you have a notification
|
||||
partner_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
|
||||
not_obj = self.pool.get('mail.notification')
|
||||
not_ids = not_obj.search(cr, uid, [
|
||||
('partner_id', '=', partner_id),
|
||||
('message_id', 'in', ids),
|
||||
], context=context)
|
||||
notified_ids = [notification.message_id.id for notification in not_obj.browse(cr, uid, not_ids, context=context)
|
||||
if notification.message_id.id in ids]
|
||||
|
||||
# check messages linked to an existing document
|
||||
model_record_ids = {}
|
||||
document_ids = []
|
||||
cr.execute('SELECT DISTINCT id, model, res_id FROM mail_message WHERE id = ANY (%s)', (ids,))
|
||||
for id, rmod, rid in cr.fetchall():
|
||||
if not (rmod and rid):
|
||||
continue
|
||||
res_ids.setdefault(rmod,set()).add(rid)
|
||||
if values:
|
||||
if 'res_model' in values and 'res_id' in values:
|
||||
res_ids.setdefault(values['res_model'],set()).add(values['res_id'])
|
||||
document_ids.append(id)
|
||||
model_record_ids.setdefault(rmod, set()).add(rid)
|
||||
for model, mids in model_record_ids.items():
|
||||
model_obj = self.pool.get(model)
|
||||
mids = model_obj.exists(cr, uid, mids)
|
||||
model_obj.check_access_rights(cr, uid, operation)
|
||||
model_obj.check_access_rule(cr, uid, mids, operation, context=context)
|
||||
|
||||
ima_obj = self.pool.get('ir.model.access')
|
||||
for model, mids in res_ids.items():
|
||||
# ignore mail messages that are not attached to a resource anymore when checking access rights
|
||||
# (resource was deleted but message was not)
|
||||
mids = self.pool.get(model).exists(cr, uid, mids)
|
||||
ima_obj.check(cr, uid, model, mode)
|
||||
self.pool.get(model).check_access_rule(cr, uid, mids, mode, context=context)
|
||||
# fall back on classic operation for other ids
|
||||
other_ids = set(ids).difference(set(notified_ids), set(document_ids))
|
||||
super(mail_message, self).check_access_rule(cr, uid, other_ids, operation, context=None)
|
||||
|
||||
def create(self, cr, uid, values, context=None):
|
||||
self.check(cr, uid, [], mode='create', context=context, values=values)
|
||||
return super(mail_message, self).create(cr, uid, values, context)
|
||||
if not values.get('message_id') and values.get('res_id') and values.get('model'):
|
||||
values['message_id'] = tools.generate_tracking_message_id('%(model)s-%(res_id)s' % values)
|
||||
newid = super(mail_message, self).create(cr, uid, values, context)
|
||||
self.notify(cr, uid, newid, context=context)
|
||||
return newid
|
||||
|
||||
def read(self, cr, uid, ids, fields_to_read=None, context=None, load='_classic_read'):
|
||||
self.check(cr, uid, ids, 'read', context=context)
|
||||
return super(mail_message, self).read(cr, uid, ids, fields_to_read, context, load)
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
# cascade-delete attachments that are directly attached to the message (should only happen
|
||||
# for mail.messages that act as parent for a standalone mail.mail record.
|
||||
attachments_to_delete = []
|
||||
for mail in self.browse(cr, uid, ids, context=context):
|
||||
for attach in mail.attachment_ids:
|
||||
if attach.res_model == 'mail.message' and attach.res_id == mail.id:
|
||||
attachments_to_delete.append(attach.id)
|
||||
if attachments_to_delete:
|
||||
self.pool.get('ir.attachment').unlink(cr, uid, attachments_to_delete, context=context)
|
||||
return super(mail_message,self).unlink(cr, uid, ids, context=context)
|
||||
|
||||
def notify(self, cr, uid, newid, context=None):
|
||||
""" Add the related record followers to the destination partner_ids.
|
||||
Call mail_notification.notify to manage the email sending
|
||||
"""
|
||||
message = self.browse(cr, uid, newid, context=context)
|
||||
partners_to_notify = set([])
|
||||
# add all partner_ids of the message
|
||||
if message.partner_ids:
|
||||
partners_to_notify |= set(partner.id for partner in message.partner_ids)
|
||||
# add all followers and set add them in partner_ids
|
||||
if message.model and message.res_id:
|
||||
record = self.pool.get(message.model).browse(cr, uid, message.res_id, context=context)
|
||||
extra_notified = set(partner.id for partner in record.message_follower_ids)
|
||||
missing_notified = extra_notified - partners_to_notify
|
||||
if missing_notified:
|
||||
message.write({'partner_ids': [(4, p_id) for p_id in missing_notified]})
|
||||
partners_to_notify |= extra_notified
|
||||
self.pool.get('mail.notification').notify(cr, uid, list(partners_to_notify), newid, context=context)
|
||||
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
"""Overridden to avoid duplicating fields that are unique to each email"""
|
||||
if default is None:
|
||||
default = {}
|
||||
self.check(cr, uid, [id], 'read', context=context)
|
||||
default.update(message_id=False, original=False, headers=False)
|
||||
return super(mail_message,self).copy(cr, uid, id, default=default, context=context)
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
self.check(cr, uid, ids, 'write', context=context, values=vals)
|
||||
return super(mail_message, self).write(cr, uid, ids, vals, context)
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
self.check(cr, uid, ids, 'unlink', context=context)
|
||||
return super(mail_message, self).unlink(cr, uid, ids, context)
|
||||
|
||||
def schedule_with_attach(self, cr, uid, email_from, email_to, subject, body, model=False, type='email',
|
||||
email_cc=None, email_bcc=None, reply_to=False, partner_ids=None, attachments=None,
|
||||
message_id=False, references=False, res_id=False, content_subtype='plain',
|
||||
headers=None, mail_server_id=False, auto_delete=False, context=None):
|
||||
""" Schedule sending a new email message, to be sent the next time the
|
||||
mail scheduler runs, or the next time :meth:`process_email_queue` is
|
||||
called explicitly.
|
||||
|
||||
:param string email_from: sender email address
|
||||
:param list email_to: list of recipient addresses (to be joined with commas)
|
||||
:param string subject: email subject (no pre-encoding/quoting necessary)
|
||||
:param string body: email body, according to the ``content_subtype``
|
||||
(by default, plaintext). If html content_subtype is used, the
|
||||
message will be automatically converted to plaintext and wrapped
|
||||
in multipart/alternative.
|
||||
:param list email_cc: optional list of string values for CC header
|
||||
(to be joined with commas)
|
||||
:param list email_bcc: optional list of string values for BCC header
|
||||
(to be joined with commas)
|
||||
:param string model: optional model name of the document this mail
|
||||
is related to (this will also be used to generate a tracking id,
|
||||
used to match any response related to the same document)
|
||||
:param int res_id: optional resource identifier this mail is related
|
||||
to (this will also be used to generate a tracking id, used to
|
||||
match any response related to the same document)
|
||||
:param string reply_to: optional value of Reply-To header
|
||||
:param partner_ids: destination partner_ids
|
||||
:param string content_subtype: optional mime content_subtype for
|
||||
the text body (usually 'plain' or 'html'), must match the format
|
||||
of the ``body`` parameter. Default is 'plain', making the content
|
||||
part of the mail "text/plain".
|
||||
:param dict attachments: map of filename to filecontents, where
|
||||
filecontents is a string containing the bytes of the attachment
|
||||
:param dict headers: optional map of headers to set on the outgoing
|
||||
mail (may override the other headers, including Subject,
|
||||
Reply-To, Message-Id, etc.)
|
||||
:param int mail_server_id: optional id of the preferred outgoing
|
||||
mail server for this mail
|
||||
:param bool auto_delete: optional flag to turn on auto-deletion of
|
||||
the message after it has been successfully sent (default to False)
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
if attachments is None:
|
||||
attachments = {}
|
||||
if partner_ids is None:
|
||||
partner_ids = []
|
||||
attachment_obj = self.pool.get('ir.attachment')
|
||||
for param in (email_to, email_cc, email_bcc):
|
||||
if param and not isinstance(param, list):
|
||||
param = [param]
|
||||
msg_vals = {
|
||||
'subject': subject,
|
||||
'date': fields.datetime.now(),
|
||||
'user_id': uid,
|
||||
'model': model,
|
||||
'res_id': res_id,
|
||||
'type': type,
|
||||
'body_text': body if content_subtype != 'html' else False,
|
||||
'body_html': body if content_subtype == 'html' else False,
|
||||
'email_from': email_from,
|
||||
'email_to': email_to and ','.join(email_to) or '',
|
||||
'email_cc': email_cc and ','.join(email_cc) or '',
|
||||
'email_bcc': email_bcc and ','.join(email_bcc) or '',
|
||||
'partner_ids': partner_ids,
|
||||
'reply_to': reply_to,
|
||||
'message_id': message_id,
|
||||
'references': references,
|
||||
'content_subtype': content_subtype,
|
||||
'headers': headers, # serialize the dict on the fly
|
||||
'mail_server_id': mail_server_id,
|
||||
'state': 'outgoing',
|
||||
'auto_delete': auto_delete
|
||||
}
|
||||
email_msg_id = self.create(cr, uid, msg_vals, context)
|
||||
attachment_ids = []
|
||||
for attachment in attachments:
|
||||
fname, fcontent = attachment
|
||||
attachment_data = {
|
||||
'name': fname,
|
||||
'datas_fname': fname,
|
||||
'datas': fcontent and fcontent.encode('base64'),
|
||||
'res_model': self._name,
|
||||
'res_id': email_msg_id,
|
||||
}
|
||||
if context.has_key('default_type'):
|
||||
del context['default_type']
|
||||
attachment_ids.append(attachment_obj.create(cr, uid, attachment_data, context))
|
||||
if attachment_ids:
|
||||
self.write(cr, uid, email_msg_id, { 'attachment_ids': [(6, 0, attachment_ids)]}, context=context)
|
||||
return email_msg_id
|
||||
|
||||
def mark_outgoing(self, cr, uid, ids, context=None):
|
||||
return self.write(cr, uid, ids, {'state':'outgoing'}, context=context)
|
||||
|
||||
def cancel(self, cr, uid, ids, context=None):
|
||||
return self.write(cr, uid, ids, {'state':'cancel'}, context=context)
|
||||
|
||||
def process_email_queue(self, cr, uid, ids=None, context=None):
|
||||
"""Send immediately queued messages, committing after each
|
||||
message is sent - this is not transactional and should
|
||||
not be called during another transaction!
|
||||
|
||||
:param list ids: optional list of emails ids to send. If passed
|
||||
no search is performed, and these ids are used
|
||||
instead.
|
||||
:param dict context: if a 'filters' key is present in context,
|
||||
this value will be used as an additional
|
||||
filter to further restrict the outgoing
|
||||
messages to send (by default all 'outgoing'
|
||||
messages are sent).
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
if not ids:
|
||||
filters = ['&', ('state', '=', 'outgoing'), ('type', '=', 'email')]
|
||||
if 'filters' in context:
|
||||
filters.extend(context['filters'])
|
||||
ids = self.search(cr, uid, filters, context=context)
|
||||
res = None
|
||||
try:
|
||||
# Force auto-commit - this is meant to be called by
|
||||
# the scheduler, and we can't allow rolling back the status
|
||||
# of previously sent emails!
|
||||
res = self.send(cr, uid, ids, auto_commit=True, context=context)
|
||||
except Exception:
|
||||
_logger.exception("Failed processing mail queue")
|
||||
return res
|
||||
|
||||
def parse_message(self, message, save_original=False, context=None):
|
||||
"""Parses a string or email.message.Message representing an
|
||||
RFC-2822 email, and returns a generic dict holding the
|
||||
message details.
|
||||
|
||||
:param message: the message to parse
|
||||
:type message: email.message.Message | string | unicode
|
||||
:param bool save_original: whether the returned dict
|
||||
should include an ``original`` entry with the base64
|
||||
encoded source of the message.
|
||||
:rtype: dict
|
||||
:return: A dict with the following structure, where each
|
||||
field may not be present if missing in original
|
||||
message::
|
||||
|
||||
{ 'message-id': msg_id,
|
||||
'subject': subject,
|
||||
'from': from,
|
||||
'to': to,
|
||||
'cc': cc,
|
||||
'headers' : { 'X-Mailer': mailer,
|
||||
#.. all X- headers...
|
||||
},
|
||||
'content_subtype': msg_mime_subtype,
|
||||
'body_text': plaintext_body
|
||||
'body_html': html_body,
|
||||
'attachments': [('file1', 'bytes'),
|
||||
('file2', 'bytes') }
|
||||
# ...
|
||||
'original': source_of_email,
|
||||
}
|
||||
"""
|
||||
msg_txt = message
|
||||
if isinstance(message, str):
|
||||
msg_txt = email.message_from_string(message)
|
||||
|
||||
# Warning: message_from_string doesn't always work correctly on unicode,
|
||||
# we must use utf-8 strings here :-(
|
||||
if isinstance(message, unicode):
|
||||
message = message.encode('utf-8')
|
||||
msg_txt = email.message_from_string(message)
|
||||
|
||||
message_id = msg_txt.get('message-id', False)
|
||||
msg = {}
|
||||
|
||||
if save_original:
|
||||
# save original, we need to be able to read the original email sometimes
|
||||
msg['original'] = message.as_string() if isinstance(message, Message) \
|
||||
else message
|
||||
msg['original'] = base64.b64encode(msg['original']) # binary fields are b64
|
||||
|
||||
if not message_id:
|
||||
# Very unusual situation, be we should be fault-tolerant here
|
||||
message_id = time.time()
|
||||
msg_txt['message-id'] = message_id
|
||||
_logger.info('Parsing Message without message-id, generating a random one: %s', message_id)
|
||||
|
||||
msg_fields = msg_txt.keys()
|
||||
msg['id'] = message_id
|
||||
msg['message-id'] = message_id
|
||||
|
||||
if 'Subject' in msg_fields:
|
||||
msg['subject'] = decode(msg_txt.get('Subject'))
|
||||
|
||||
if 'Content-Type' in msg_fields:
|
||||
msg['content-type'] = msg_txt.get('Content-Type')
|
||||
|
||||
if 'From' in msg_fields:
|
||||
msg['from'] = decode(msg_txt.get('From') or msg_txt.get_unixfrom())
|
||||
|
||||
if 'To' in msg_fields:
|
||||
msg['to'] = decode(msg_txt.get('To'))
|
||||
|
||||
if 'Delivered-To' in msg_fields:
|
||||
msg['to'] = decode(msg_txt.get('Delivered-To'))
|
||||
|
||||
if 'CC' in msg_fields:
|
||||
msg['cc'] = decode(msg_txt.get('CC'))
|
||||
|
||||
if 'Cc' in msg_fields:
|
||||
msg['cc'] = decode(msg_txt.get('Cc'))
|
||||
|
||||
if 'Reply-To' in msg_fields:
|
||||
msg['reply'] = decode(msg_txt.get('Reply-To'))
|
||||
|
||||
if 'Date' in msg_fields:
|
||||
date_hdr = decode(msg_txt.get('Date'))
|
||||
# convert from email timezone to server timezone
|
||||
date_server_datetime = dateutil.parser.parse(date_hdr).astimezone(pytz.timezone(tools.get_server_timezone()))
|
||||
date_server_datetime_str = date_server_datetime.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
msg['date'] = date_server_datetime_str
|
||||
|
||||
if 'Content-Transfer-Encoding' in msg_fields:
|
||||
msg['encoding'] = msg_txt.get('Content-Transfer-Encoding')
|
||||
|
||||
if 'References' in msg_fields:
|
||||
msg['references'] = msg_txt.get('References')
|
||||
|
||||
if 'In-Reply-To' in msg_fields:
|
||||
msg['in-reply-to'] = msg_txt.get('In-Reply-To')
|
||||
|
||||
msg['headers'] = {}
|
||||
msg['content_subtype'] = 'plain'
|
||||
for item in msg_txt.items():
|
||||
if item[0].startswith('X-'):
|
||||
msg['headers'].update({item[0]: item[1]})
|
||||
if not msg_txt.is_multipart() or 'text/plain' in msg.get('content-type', ''):
|
||||
encoding = msg_txt.get_content_charset()
|
||||
body = msg_txt.get_payload(decode=True)
|
||||
if 'text/html' in msg.get('content-type', ''):
|
||||
msg['body_html'] = body
|
||||
msg['content_subtype'] = 'html'
|
||||
if body:
|
||||
body = tools.html2plaintext(body)
|
||||
msg['body_text'] = tools.ustr(body, encoding)
|
||||
|
||||
attachments = []
|
||||
if msg_txt.is_multipart() or 'multipart/alternative' in msg.get('content-type', ''):
|
||||
body = ""
|
||||
if 'multipart/alternative' in msg.get('content-type', ''):
|
||||
msg['content_subtype'] = 'alternative'
|
||||
else:
|
||||
msg['content_subtype'] = 'mixed'
|
||||
for part in msg_txt.walk():
|
||||
if part.get_content_maintype() == 'multipart':
|
||||
continue
|
||||
|
||||
encoding = part.get_content_charset()
|
||||
filename = part.get_filename()
|
||||
if part.get_content_maintype()=='text':
|
||||
content = part.get_payload(decode=True)
|
||||
if filename:
|
||||
attachments.append((filename, content))
|
||||
content = tools.ustr(content, encoding)
|
||||
if part.get_content_subtype() == 'html':
|
||||
msg['body_html'] = content
|
||||
msg['content_subtype'] = 'html' # html version prevails
|
||||
body = tools.ustr(tools.html2plaintext(content))
|
||||
body = body.replace(' ', '')
|
||||
elif part.get_content_subtype() == 'plain':
|
||||
body = content
|
||||
elif part.get_content_maintype() in ('application', 'image'):
|
||||
if filename :
|
||||
attachments.append((filename,part.get_payload(decode=True)))
|
||||
else:
|
||||
res = part.get_payload(decode=True)
|
||||
body += tools.ustr(res, encoding)
|
||||
|
||||
msg['body_text'] = body
|
||||
msg['attachments'] = attachments
|
||||
|
||||
# for backwards compatibility:
|
||||
msg['body'] = msg['body_text']
|
||||
msg['sub_type'] = msg['content_subtype'] or 'plain'
|
||||
return msg
|
||||
|
||||
def _postprocess_sent_message(self, cr, uid, message, context=None):
|
||||
"""Perform any post-processing necessary after sending ``message``
|
||||
successfully, including deleting it completely along with its
|
||||
attachment if the ``auto_delete`` flag of the message was set.
|
||||
Overridden by subclasses for extra post-processing behaviors.
|
||||
|
||||
:param browse_record message: the message that was just sent
|
||||
:return: True
|
||||
"""
|
||||
if message.auto_delete:
|
||||
self.pool.get('ir.attachment').unlink(cr, uid,
|
||||
[x.id for x in message.attachment_ids
|
||||
if x.res_model == self._name and x.res_id == message.id],
|
||||
context=context)
|
||||
message.unlink()
|
||||
return True
|
||||
|
||||
def send(self, cr, uid, ids, auto_commit=False, context=None):
|
||||
"""Sends the selected emails immediately, ignoring their current
|
||||
state (mails that have already been sent should not be passed
|
||||
unless they should actually be re-sent).
|
||||
Emails successfully delivered are marked as 'sent', and those
|
||||
that fail to be deliver are marked as 'exception', and the
|
||||
corresponding error message is output in the server logs.
|
||||
|
||||
:param bool auto_commit: whether to force a commit of the message
|
||||
status after sending each message (meant
|
||||
only for processing by the scheduler),
|
||||
should never be True during normal
|
||||
transactions (default: False)
|
||||
:return: True
|
||||
"""
|
||||
ir_mail_server = self.pool.get('ir.mail_server')
|
||||
self.write(cr, uid, ids, {'state': 'outgoing'}, context=context)
|
||||
for message in self.browse(cr, uid, ids, context=context):
|
||||
try:
|
||||
attachments = []
|
||||
for attach in message.attachment_ids:
|
||||
attachments.append((attach.datas_fname, base64.b64decode(attach.datas)))
|
||||
|
||||
body = message.body_html if message.content_subtype == 'html' else message.body_text
|
||||
body_alternative = None
|
||||
content_subtype_alternative = None
|
||||
if message.content_subtype == 'html' and message.body_text:
|
||||
# we have a plain text alternative prepared, pass it to
|
||||
# build_message instead of letting it build one
|
||||
body_alternative = message.body_text
|
||||
content_subtype_alternative = 'plain'
|
||||
|
||||
# handle destination_partners
|
||||
partner_ids_email_to = ''
|
||||
for partner in message.partner_ids:
|
||||
partner_ids_email_to += '%s ' % (partner.email or '')
|
||||
message_email_to = '%s %s' % (partner_ids_email_to, message.email_to or '')
|
||||
|
||||
# build an RFC2822 email.message.Message object and send it
|
||||
# without queuing
|
||||
msg = ir_mail_server.build_email(
|
||||
email_from=message.email_from,
|
||||
email_to=mail_tools_to_email(message_email_to),
|
||||
subject=message.subject,
|
||||
body=body,
|
||||
body_alternative=body_alternative,
|
||||
email_cc=mail_tools_to_email(message.email_cc),
|
||||
email_bcc=mail_tools_to_email(message.email_bcc),
|
||||
reply_to=message.reply_to,
|
||||
attachments=attachments, message_id=message.message_id,
|
||||
references = message.references,
|
||||
object_id=message.res_id and ('%s-%s' % (message.res_id,message.model)),
|
||||
subtype=message.content_subtype,
|
||||
subtype_alternative=content_subtype_alternative,
|
||||
headers=message.headers and ast.literal_eval(message.headers))
|
||||
res = ir_mail_server.send_email(cr, uid, msg,
|
||||
mail_server_id=message.mail_server_id.id,
|
||||
context=context)
|
||||
if res:
|
||||
message.write({'state':'sent', 'message_id': res, 'email_to': message_email_to})
|
||||
else:
|
||||
message.write({'state':'exception', 'email_to': message_email_to})
|
||||
message.refresh()
|
||||
if message.state == 'sent':
|
||||
self._postprocess_sent_message(cr, uid, message, context=context)
|
||||
except Exception:
|
||||
_logger.exception('failed sending mail.message %s', message.id)
|
||||
message.write({'state':'exception'})
|
||||
|
||||
if auto_commit == True:
|
||||
cr.commit()
|
||||
return True
|
||||
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
default.update(message_id=False, headers=False)
|
||||
return super(mail_message, self).copy(cr, uid, id, default=default, context=context)
|
||||
|
|
|
@ -2,15 +2,15 @@
|
|||
<openerp>
|
||||
<data>
|
||||
<!-- mail.message tree: short view !-->
|
||||
<record model="ir.ui.view" id="view_message_tree_short">
|
||||
<field name="name">mail.message.tree.short</field>
|
||||
<record model="ir.ui.view" id="view_message_tree">
|
||||
<field name="name">mail.message.tree</field>
|
||||
<field name="model">mail.message</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Messages">
|
||||
<field name="date"/>
|
||||
<field name="subject"/>
|
||||
<field name="user_id"/>
|
||||
<field name="author_id"/>
|
||||
<field name="model"/>
|
||||
<field name="res_id"/>
|
||||
</tree>
|
||||
|
@ -18,8 +18,8 @@
|
|||
</record>
|
||||
|
||||
<!-- mail.message form: short view !-->
|
||||
<record model="ir.ui.view" id="view_message_form_short">
|
||||
<field name="name">mail.message.form.short</field>
|
||||
<record model="ir.ui.view" id="view_message_form">
|
||||
<field name="name">mail.message.form</field>
|
||||
<field name="model">mail.message</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="arch" type="xml">
|
||||
|
@ -28,10 +28,9 @@
|
|||
<group>
|
||||
<group>
|
||||
<field name="subject"/>
|
||||
<field name="user_id"/>
|
||||
<field name="author_id"/>
|
||||
<field name="date"/>
|
||||
<field name="type"/>
|
||||
<field name="content_subtype"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="model"/>
|
||||
|
@ -40,45 +39,22 @@
|
|||
<field name="partner_ids" widget="many2many_tags"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Body (Rich)">
|
||||
<field name="body_html"/>
|
||||
</page>
|
||||
<page string="Body (Plain)">
|
||||
<field name="body_text" widget="text"/>
|
||||
</page>
|
||||
</notebook>
|
||||
<field name="body"/>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- mail.message search: short view !-->
|
||||
<record model="ir.ui.view" id="view_message_search_short">
|
||||
<field name="name">mail.message.search.short</field>
|
||||
<field name="model">mail.message</field>
|
||||
<field name="priority">20</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Messages Search">
|
||||
<field name="model" string="Message"/>
|
||||
<field name="date"/>
|
||||
<field name="user_id"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- mail.message search: wall view !-->
|
||||
<record model="ir.ui.view" id="view_message_search_wall">
|
||||
<field name="name">mail.message.search.wall</field>
|
||||
<record model="ir.ui.view" id="view_message_search">
|
||||
<field name="name">mail.message.search</field>
|
||||
<field name="model">mail.message</field>
|
||||
<field name="priority">25</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Messages Search">
|
||||
<field name="subject" string="Content" filter_domain="['|', ('subject', 'ilike', self), ('body', 'ilike', self)]" />
|
||||
<field name="type"/>
|
||||
<filter icon="terp-personal+" string="My Feeds"
|
||||
name="my_feeds" help="My Feeds"
|
||||
domain="[('user_id','=',uid)]"/>
|
||||
<field name="author_id"/>
|
||||
<filter icon="terp-personal+" string="Comments"
|
||||
name="comments" help="Comments"
|
||||
domain="[('type', '=', 'comment')]"/>
|
||||
|
@ -88,159 +64,17 @@
|
|||
<filter icon="terp-personal+" string="Emails"
|
||||
name="emails" help="Emails"
|
||||
domain="[('type', '=', 'email')]"/>
|
||||
<filter icon="terp-go-today" string="Today"
|
||||
name="today" help="Today"
|
||||
domain="[ ('date', '<=', datetime.date.today().strftime('%%Y-%%m-%%d 23:59:59')),
|
||||
('date', '>=', datetime.date.today().strftime('%%Y-%%m-%%d 00:00:00'))
|
||||
]"/>
|
||||
<filter icon="terp-go-week" string="This week"
|
||||
name="7_days" help="This week"
|
||||
domain="[ ('date', '<=', datetime.date.today().strftime('%%Y-%%m-%%d 23:59:59')),
|
||||
('date', '>=', (datetime.date.today()-datetime.timedelta(days=7)).strftime('%%Y-%%m-%%d 00:00:00'))
|
||||
]"/>
|
||||
<field name="user_id"/>
|
||||
<field name="author_id"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_email_message_form">
|
||||
<field name="name">mail.message.form</field>
|
||||
<field name="model">mail.message</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Email message" version="7.0">
|
||||
<sheet>
|
||||
<label for="subject" class="oe_edit_only"/>
|
||||
<h2><field name="subject"/></h2>
|
||||
<div>
|
||||
by <field name="user_id" class="oe_inline" string="User"/> on <field name="date" class="oe_inline"/>
|
||||
<button name="%(action_email_compose_message_wizard)d" string="Reply" type="action" icon="terp-mail-replied"
|
||||
context="{'mail.compose.message.mode':'reply', 'message_id':active_id}" states='received,sent,exception,cancel'/>
|
||||
</div>
|
||||
<notebook colspan="4">
|
||||
<page string="Message Details">
|
||||
<group>
|
||||
<group>
|
||||
<field name="email_from"/>
|
||||
<field name="email_to"/>
|
||||
<field name="email_cc"/>
|
||||
<field name="email_bcc"/>
|
||||
<field name="reply_to"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="partner_id" readonly="1"/>
|
||||
<field name="partner_ids" widget="many2many_tags"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Body (Rich)">
|
||||
<field name="body_html"/>
|
||||
</page>
|
||||
<page string="Body (Plain)">
|
||||
<field name="body_text" widget="text"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</page>
|
||||
<page string="Advanced" groups="base.group_no_one">
|
||||
<group>
|
||||
<group>
|
||||
<field name="auto_delete"/>
|
||||
<field name="type"/>
|
||||
<field name="content_subtype"/>
|
||||
<field name="state" colspan="2"/>
|
||||
<field name="mail_server_id"/>
|
||||
<field name="original"/>
|
||||
<field name="model"/>
|
||||
<field name="res_id"/>
|
||||
<button name="open_document" string="Open" type="object" icon="gtk-jump-to" colspan="2"
|
||||
attrs="{'invisible':['|', ('model', '=', ''), ('res_id', '=', False)]}"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="message_id"/>
|
||||
<field name="references"/>
|
||||
<field name="headers"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Attachments">
|
||||
<field name="attachment_ids"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_email_message_tree">
|
||||
<field name="name">mail.message.tree</field>
|
||||
<field name="model">mail.message</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Emails" colors="grey:state in ('sent', 'cancel');blue:state=='outgoing';red:state=='exception';black:state=='received'">
|
||||
<field name="date"/>
|
||||
<field name="subject"/>
|
||||
<field name="email_from"/>
|
||||
<field name="user_id" string="User"/>
|
||||
<field name="message_id" invisible="1"/>
|
||||
<field name="partner_id" invisible="1"/>
|
||||
<field name="model" invisible="1"/>
|
||||
<field name="res_id" invisible="1"/>
|
||||
<field name="state"/>
|
||||
<button name="send" string="Send Now" type="object" icon="gtk-media-play" states='outgoing'/>
|
||||
<button name="mark_outgoing" string="Retry" type="object" icon="gtk-redo" states='exception,cancel'/>
|
||||
<button name="cancel" string="Cancel" type="object" icon="terp-gtk-stop" states='outgoing'/>
|
||||
<button name="open_document" string="Open Related Document" type="object" icon="gtk-jump-to"/>
|
||||
<button name="open_attachment" string="Open Attachments" type="object" icon="gtk-jump-to"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_email_message_search">
|
||||
<field name="name">mail.message.search</field>
|
||||
<field name="model">mail.message</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Email Search">
|
||||
<field name="email_from" filter_domain="['|' '|',('email_from','ilike',self), ('email_to','ilike',self), ('subject','ilike',self)]" string="Email"/>
|
||||
<field name="date"/>
|
||||
<filter icon="terp-camera_test" name="received" string="Received" domain="[('state','=','received')]"/>
|
||||
<filter icon="terp-call-start" name="outgoing" string="Outgoing" domain="[('state','=','outgoing')]"/>
|
||||
<filter icon="terp-check" name="sent" string="Sent" domain="[('state','=','sent')]"/>
|
||||
<filter icon="terp-gtk-stop" name="exception" string="Failed" domain="[('state','=','exception')]"/>
|
||||
<separator/>
|
||||
<filter icon="terp-camera_test" name="type_email" string="Email" domain="[('type','=','email')]"/>
|
||||
<filter icon="terp-camera_test" name="type_comment" string="Comment" domain="[('type','=','comment')]"/>
|
||||
<filter icon="terp-camera_test" name="type_notification" string="Notification" domain="[('type','=','notification')]"/>
|
||||
<group expand="0" string="Extended Filters...">
|
||||
<field name="user_id" string="User"/>
|
||||
<field name="partner_id" string="Partner Name"/>
|
||||
<field name="model"/>
|
||||
<field name="res_id"/>
|
||||
</group>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Status" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}"/>
|
||||
<filter string="Partner" icon="terp-partner" domain="[]" context="{'group_by':'partner_id'}"/>
|
||||
<filter string="User" name="User" icon="terp-personal" context="{'group_by':'user_id'}"/>
|
||||
<filter string="Thread" icon="terp-mail-" domain="[]" context="{'group_by':'message_id'}"/>
|
||||
<filter string="Month" help="Creation Month" icon="terp-go-month" domain="[]" context="{'group_by':'date'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_all_messages_short" model="ir.actions.act_window">
|
||||
<field name="name">Messages</field>
|
||||
<field name="res_model">mail.message</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{'tree_view_ref': 'mail.view_message_tree_short', 'form_view_ref': 'mail.view_message_form_short'}</field>
|
||||
<field name="search_view_id" ref="view_message_search_short"/>
|
||||
</record>
|
||||
|
||||
<record id="action_view_mail_message" model="ir.actions.act_window">
|
||||
<field name="name">Messages</field>
|
||||
<field name="res_model">mail.message</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{'search_default_received': 1, 'search_default_type_email': 1}</field>
|
||||
<field name="search_view_id" ref="view_email_message_search"/>
|
||||
<field name="search_view_id" ref="view_message_search"/>
|
||||
</record>
|
||||
|
||||
<act_window domain="[('partner_id', '=', active_id), ('email_from', '!=', False)]"
|
||||
|
@ -248,24 +82,23 @@
|
|||
id="act_res_partner_emails" name="Emails"
|
||||
res_model="mail.message"
|
||||
src_model="res.partner"
|
||||
view_id="view_email_message_tree"/>
|
||||
view_id="view_message_tree"/>
|
||||
|
||||
<!-- Add menu entry in Settings/Email -->
|
||||
<menuitem name="Messages" id="menu_email_message" parent="base.menu_email" action="action_view_mail_message" />
|
||||
|
||||
<!-- Add menu entry in Settings/Email -->
|
||||
<menuitem name="Feeds" id="menu_email_message_all" parent="base.menu_email" action="action_view_all_messages_short" groups="base.group_no_one"/>
|
||||
<menuitem name="Messages" id="menu_mail_message" parent="base.menu_email" action="action_view_mail_message"/>
|
||||
|
||||
<record id="action_mail_all_feeds" model="ir.actions.client">
|
||||
<field name="name">News Feed</field>
|
||||
<field name="tag">mail.wall</field>
|
||||
<field name="params" eval="{'search_view_id': ref('view_message_search_wall')}"/>
|
||||
<field name="params" eval=""{'domain': [('notification_ids.partner_id.user_ids', 'in', [uid])],
|
||||
'context': {'default_model': 'res.users', 'default_res_id': uid} }""/>
|
||||
</record>
|
||||
|
||||
<record id="action_mail_my_feeds" model="ir.actions.client">
|
||||
<field name="name">My Feeds</field>
|
||||
<field name="tag">mail.wall</field>
|
||||
<field name="params" eval="{'search_view_id': ref('view_message_search_wall'), 'my_feeds': True}"/>
|
||||
<field name="params" eval=""{'domain': [('author_id.user_ids', 'in', [uid])],
|
||||
'context': {'default_model': 'res.users', 'default_res_id': uid} }""/>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -19,29 +19,27 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from osv import osv
|
||||
from osv import osv, fields
|
||||
|
||||
class res_partner_mail(osv.Model):
|
||||
""" Inherits partner and adds CRM information in the partner form """
|
||||
""" Update partner to add a field about notification preferences """
|
||||
_name = "res.partner"
|
||||
_inherit = ['res.partner', 'mail.thread']
|
||||
|
||||
def message_search_get_domain(self, cr, uid, ids, context=None):
|
||||
""" Override of message_search_get_domain for partner discussion page.
|
||||
The purpose is to add messages directly sent to the partner. It also
|
||||
adds messages pushed to the related user, if any, using @login.
|
||||
"""
|
||||
initial_domain = super(res_partner_mail, self).message_search_get_domain(cr, uid, ids, context=context)
|
||||
# to avoid models inheriting from res.partner
|
||||
if self._name != 'res.partner':
|
||||
return initial_domain
|
||||
# add message linked to the partner
|
||||
search_domain = ['|'] + initial_domain + ['|', ('partner_id', 'in', ids), ('partner_ids', 'in', ids)]
|
||||
# if partner is linked to a user: find @login
|
||||
res_users_obj = self.pool.get('res.users')
|
||||
user_ids = res_users_obj.search(cr, uid, [('partner_id', 'in', ids)], context=context)
|
||||
for user in res_users_obj.browse(cr, uid, user_ids, context=context):
|
||||
search_domain = ['|'] + search_domain + ['|', ('body_text', 'like', '@%s' % (user.login)), ('body_html', 'like', '@%s' % (user.login))]
|
||||
return search_domain
|
||||
_columns = {
|
||||
'notification_email_send': fields.selection([
|
||||
('all', 'All feeds'),
|
||||
('comment', 'Comments and Emails'),
|
||||
('email', 'Emails only'),
|
||||
('none', 'Never')
|
||||
], 'Receive Feeds by Email', required=True,
|
||||
help="Choose in which case you want to receive an email when you "\
|
||||
"receive new feeds."),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'notification_email_send': lambda *args: 'comment'
|
||||
}
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue