[MERGE] Forward porting of the 7.0 addons branch inside trunk until rev 8561.
bzr revid: tde@openerp.com-20130110125129-9g99h3heldcnspfh
This commit is contained in:
commit
fb3dd0ebc2
|
@ -1463,6 +1463,7 @@ class account_move(osv.osv):
|
|||
line_id = self.pool.get('account.move.line').create(cr, uid, {
|
||||
'name': _(mode.capitalize()+' Centralisation'),
|
||||
'centralisation': mode,
|
||||
'partner_id': False,
|
||||
'account_id': account_id,
|
||||
'move_id': move.id,
|
||||
'journal_id': move.journal_id.id,
|
||||
|
@ -1501,6 +1502,7 @@ class account_move(osv.osv):
|
|||
line_id = self.pool.get('account.move.line').create(cr, uid, {
|
||||
'name': _('Currency Adjustment'),
|
||||
'centralisation': 'currency',
|
||||
'partner_id': False,
|
||||
'account_id': account_id,
|
||||
'move_id': move.id,
|
||||
'journal_id': move.journal_id.id,
|
||||
|
|
|
@ -1484,11 +1484,10 @@ class account_invoice_line(osv.osv):
|
|||
new_price = res_final['value']['price_unit'] * currency.rate
|
||||
res_final['value']['price_unit'] = new_price
|
||||
|
||||
if result['uos_id'] != res.uom_id.id:
|
||||
selected_uom = self.pool.get('product.uom_id').browse(cr, uid, result['uos_id'], context=context)
|
||||
if res.uom_id.category_id.id == selected_uom.category_id.id:
|
||||
new_price = res_final['value']['price_unit'] * uom_id.factor_inv
|
||||
res_final['value']['price_unit'] = new_price
|
||||
if result['uos_id'] and result['uos_id'] != res.uom_id.id:
|
||||
selected_uom = self.pool.get('product.uom').browse(cr, uid, result['uos_id'], context=context)
|
||||
new_price = self.pool.get('product.uom')._compute_price(cr, uid, res.uom_id.id, res_final['value']['price_unit'], result['uos_id'])
|
||||
res_final['value']['price_unit'] = new_price
|
||||
return res_final
|
||||
|
||||
def uos_id_change(self, cr, uid, ids, product, uom, qty=0, name='', type='out_invoice', partner_id=False, fposition_id=False, price_unit=False, currency_id=False, context=None, company_id=None):
|
||||
|
|
|
@ -97,7 +97,7 @@
|
|||
<form string="Account Period" version="7.0">
|
||||
<header>
|
||||
<button string="Close Period" name="%(account.action_account_period_close)d" type="action" class="oe_highlight" states="draft"/>
|
||||
<button name="action_draft" states="done" string="Set to Draft" type="object" groups="account.group_account_manager"/>
|
||||
<button name="action_draft" states="done" string="Re-Open Period" type="object" groups="account.group_account_manager"/>
|
||||
<field name="state" widget="statusbar" nolabel="1"/>
|
||||
</header>
|
||||
<sheet>
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
<field name="name">account.open.closed.fiscalyear.form</field>
|
||||
<field name="model">account.open.closed.fiscalyear</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Cancel Fiscal Year Opening Entries" version="7.0">
|
||||
<separator string="Cancel Fiscal Year Opening Entries"/>
|
||||
<form string="Cancel Fiscal Year Closing Entries" version="7.0">
|
||||
<separator string="Cancel Fiscal Year Closing Entries"/>
|
||||
<p class="oe_grey">
|
||||
This wizard will remove the end of year journal entries of selected fiscal year. Note that you can run this wizard many times for the same fiscal year.
|
||||
</p>
|
||||
|
@ -14,7 +14,7 @@ This wizard will remove the end of year journal entries of selected fiscal year.
|
|||
<field name="fyear_id" domain="[('state','=','draft'), ('end_journal_period_id', '!=', False)]"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Cancel Opening Entries" name="remove_entries" type="object" class="oe_highlight"/>
|
||||
<button string="Cancel Closing Entries" name="remove_entries" type="object" class="oe_highlight"/>
|
||||
or
|
||||
<button string="Discard" class="oe_link" special="cancel"/>
|
||||
</footer>
|
||||
|
@ -23,7 +23,7 @@ This wizard will remove the end of year journal entries of selected fiscal year.
|
|||
</record>
|
||||
|
||||
<record id="action_account_open_closed_fiscalyear" model="ir.actions.act_window">
|
||||
<field name="name">Cancel Opening Entries</field>
|
||||
<field name="name">Cancel Closing Entries</field>
|
||||
<field name="res_model">account.open.closed.fiscalyear</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
|
|
|
@ -52,7 +52,7 @@ class res_partner(osv.Model):
|
|||
(not partner.signup_expiration or dt <= partner.signup_expiration)
|
||||
return res
|
||||
|
||||
def _get_signup_url_for_action(self, cr, uid, ids, action='login', view_type=None, menu_id=None, res_id=None, context=None):
|
||||
def _get_signup_url_for_action(self, cr, uid, ids, action='login', view_type=None, menu_id=None, res_id=None, model=None, context=None):
|
||||
""" generate a signup url for the given partner ids and action, possibly overriding
|
||||
the url state components (menu_id, id, view_type) """
|
||||
res = dict.fromkeys(ids, False)
|
||||
|
@ -61,6 +61,7 @@ class res_partner(osv.Model):
|
|||
# when required, make sure the partner has a valid signup token
|
||||
if context and context.get('signup_valid') and not partner.user_ids:
|
||||
self.signup_prepare(cr, uid, [partner.id], context=context)
|
||||
partner.refresh()
|
||||
|
||||
# the parameters to encode for the query and fragment part of url
|
||||
query = {'db': cr.dbname}
|
||||
|
@ -78,6 +79,8 @@ class res_partner(osv.Model):
|
|||
fragment['view_type'] = view_type
|
||||
if menu_id:
|
||||
fragment['menu_id'] = menu_id
|
||||
if model:
|
||||
fragment['model'] = model
|
||||
if res_id:
|
||||
fragment['id'] = res_id
|
||||
|
||||
|
|
|
@ -397,7 +397,8 @@ class crm_lead(base_stage, format_address, osv.osv):
|
|||
search_domain += [('|')] * len(section_ids)
|
||||
for section_id in section_ids:
|
||||
search_domain.append(('section_ids', '=', section_id))
|
||||
search_domain.append(('case_default', '=', True))
|
||||
else:
|
||||
search_domain.append(('case_default', '=', True))
|
||||
# AND with cases types
|
||||
search_domain.append(('type', 'in', types))
|
||||
# AND with the domain in parameter
|
||||
|
@ -423,15 +424,15 @@ class crm_lead(base_stage, format_address, osv.osv):
|
|||
def case_mark_lost(self, cr, uid, ids, context=None):
|
||||
""" Mark the case as lost: state=cancel and probability=0 """
|
||||
for lead in self.browse(cr, uid, ids):
|
||||
stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 0.0)], context=context)
|
||||
stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 0.0),('on_change','=',True)], context=context)
|
||||
if stage_id:
|
||||
self.case_set(cr, uid, [lead.id], values_to_update={'probability': 0.0}, new_stage_id=stage_id, context=context)
|
||||
return True
|
||||
|
||||
def case_mark_won(self, cr, uid, ids, context=None):
|
||||
""" Mark the case as lost: state=done and probability=100 """
|
||||
""" Mark the case as won: state=done and probability=100 """
|
||||
for lead in self.browse(cr, uid, ids):
|
||||
stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 100.0)], context=context)
|
||||
stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 100.0),('on_change','=',True)], context=context)
|
||||
if stage_id:
|
||||
self.case_set(cr, uid, [lead.id], values_to_update={'probability': 100.0}, new_stage_id=stage_id, context=context)
|
||||
return True
|
||||
|
@ -677,29 +678,23 @@ class crm_lead(base_stage, format_address, osv.osv):
|
|||
contact_id = False
|
||||
if customer:
|
||||
contact_id = self.pool.get('res.partner').address_get(cr, uid, [customer.id])['default']
|
||||
|
||||
if not section_id:
|
||||
section_id = lead.section_id and lead.section_id.id or False
|
||||
|
||||
if section_id:
|
||||
stage_ids = crm_stage.search(cr, uid, [('sequence', '>=', 1), ('section_ids', '=', section_id), ('probability', '>', 0)])
|
||||
else:
|
||||
stage_ids = crm_stage.search(cr, uid, [('sequence', '>=', 1), ('probability', '>', 0)])
|
||||
stage_id = stage_ids and stage_ids[0] or False
|
||||
|
||||
return {
|
||||
val = {
|
||||
'planned_revenue': lead.planned_revenue,
|
||||
'probability': lead.probability,
|
||||
'name': lead.name,
|
||||
'partner_id': customer and customer.id or False,
|
||||
'user_id': (lead.user_id and lead.user_id.id),
|
||||
'type': 'opportunity',
|
||||
'stage_id': stage_id or False,
|
||||
'date_action': fields.datetime.now(),
|
||||
'date_open': fields.datetime.now(),
|
||||
'email_from': customer and customer.email or lead.email_from,
|
||||
'phone': customer and customer.phone or lead.phone,
|
||||
}
|
||||
if not lead.stage_id or lead.stage_id.type=='lead':
|
||||
val['stage_id'] = self.stage_find(cr, uid, [lead], section_id, [('state', '=', 'draft'),('type', 'in', ('opportunity','both'))], context=context)
|
||||
return val
|
||||
|
||||
def convert_opportunity(self, cr, uid, ids, partner_id, user_ids=False, section_id=False, context=None):
|
||||
customer = False
|
||||
|
@ -966,6 +961,11 @@ class crm_lead(base_stage, format_address, osv.osv):
|
|||
# Mail Gateway
|
||||
# ----------------------------------------
|
||||
|
||||
def message_get_reply_to(self, cr, uid, ids, context=None):
|
||||
""" Override to get the reply_to of the parent project. """
|
||||
return [lead.section_id.message_get_reply_to()[0] if lead.section_id else False
|
||||
for lead in self.browse(cr, uid, ids, context=context)]
|
||||
|
||||
def message_new(self, cr, uid, msg, custom_values=None, context=None):
|
||||
""" Overrides mail_thread message_new that is called by the mailgateway
|
||||
through message_process.
|
||||
|
|
|
@ -65,6 +65,7 @@
|
|||
<field name="name">Lost</field>
|
||||
<field eval="1" name="case_default"/>
|
||||
<field eval="True" name="fold"/>
|
||||
<field eval="1" name="on_change"/>
|
||||
<field name="state">cancel</field>
|
||||
<field eval="'0'" name="probability"/>
|
||||
<field eval="'17'" name="sequence"/>
|
||||
|
|
|
@ -514,7 +514,7 @@
|
|||
<field name="type_id" invisible="1"/>
|
||||
<field name="stage_id"/>
|
||||
<field name="planned_revenue" sum="Expected Revenues"/>
|
||||
<field name="probability" widget="progressbar" avg="Avg. of Probability"/>
|
||||
<field name="probability" avg="Avg. of Probability"/>
|
||||
<field name="section_id" invisible="context.get('invisible_section', True)"/>
|
||||
<field name="user_id"/>
|
||||
<field name="priority" invisible="1"/>
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
<label for="content" string="Template"/>
|
||||
that will be used as a content template for all new page of this category.
|
||||
</div>
|
||||
<field name="content" placeholder="e.g. Once upon a time..." class="oe_edit_only"/>
|
||||
<field name="content" placeholder="e.g. Once upon a time..." class="oe_edit_only" widget="html"/>
|
||||
<div class="oe_document_page">
|
||||
<field name="display_content" widget="html" class="oe_view_only" options='{"safe": True}'/>
|
||||
</div>
|
||||
|
|
|
@ -483,15 +483,18 @@ class fleet_vehicle_log_fuel(osv.Model):
|
|||
#make any difference between 3.0 and 3). This cause a problem if you encode, for example, 2 liters at 1.5 per
|
||||
#liter => total is computed as 3.0, then trigger an onchange that recomputes price_per_liter as 3/2=1 (instead
|
||||
#of 3.0/2=1.5)
|
||||
#If there is no change in the result, we return an empty dict to prevent an infinite loop due to the 3 intertwine
|
||||
#onchange. And in order to verify that there is no change in the result, we have to limit the precision of the
|
||||
#computation to 2 decimal
|
||||
liter = float(liter)
|
||||
price_per_liter = float(price_per_liter)
|
||||
amount = float(amount)
|
||||
if liter > 0 and price_per_liter > 0:
|
||||
return {'value' : {'amount' : liter * price_per_liter,}}
|
||||
elif liter > 0 and amount > 0:
|
||||
return {'value' : {'price_per_liter' : amount / liter,}}
|
||||
elif price_per_liter > 0 and amount > 0:
|
||||
return {'value' : {'liter' : amount / price_per_liter,}}
|
||||
if liter > 0 and price_per_liter > 0 and round(liter*price_per_liter,2) != amount:
|
||||
return {'value' : {'amount' : round(liter * price_per_liter,2),}}
|
||||
elif amount > 0 and liter > 0 and round(amount/liter,2) != price_per_liter:
|
||||
return {'value' : {'price_per_liter' : round(amount / liter,2),}}
|
||||
elif amount > 0 and price_per_liter > 0 and round(amount/price_per_liter,2) != liter:
|
||||
return {'value' : {'liter' : round(amount / price_per_liter,2),}}
|
||||
else :
|
||||
return {}
|
||||
|
||||
|
@ -500,15 +503,18 @@ class fleet_vehicle_log_fuel(osv.Model):
|
|||
#make any difference between 3.0 and 3). This cause a problem if you encode, for example, 2 liters at 1.5 per
|
||||
#liter => total is computed as 3.0, then trigger an onchange that recomputes price_per_liter as 3/2=1 (instead
|
||||
#of 3.0/2=1.5)
|
||||
#If there is no change in the result, we return an empty dict to prevent an infinite loop due to the 3 intertwine
|
||||
#onchange. And in order to verify that there is no change in the result, we have to limit the precision of the
|
||||
#computation to 2 decimal
|
||||
liter = float(liter)
|
||||
price_per_liter = float(price_per_liter)
|
||||
amount = float(amount)
|
||||
if price_per_liter > 0 and liter > 0:
|
||||
return {'value' : {'amount' : liter * price_per_liter,}}
|
||||
elif price_per_liter > 0 and amount > 0:
|
||||
return {'value' : {'liter' : amount / price_per_liter,}}
|
||||
elif liter > 0 and amount > 0:
|
||||
return {'value' : {'price_per_liter' : amount / liter,}}
|
||||
if liter > 0 and price_per_liter > 0 and round(liter*price_per_liter,2) != amount:
|
||||
return {'value' : {'amount' : round(liter * price_per_liter,2),}}
|
||||
elif amount > 0 and price_per_liter > 0 and round(amount/price_per_liter,2) != liter:
|
||||
return {'value' : {'liter' : round(amount / price_per_liter,2),}}
|
||||
elif amount > 0 and liter > 0 and round(amount/liter,2) != price_per_liter:
|
||||
return {'value' : {'price_per_liter' : round(amount / liter,2),}}
|
||||
else :
|
||||
return {}
|
||||
|
||||
|
@ -517,16 +523,20 @@ class fleet_vehicle_log_fuel(osv.Model):
|
|||
#make any difference between 3.0 and 3). This cause a problem if you encode, for example, 2 liters at 1.5 per
|
||||
#liter => total is computed as 3.0, then trigger an onchange that recomputes price_per_liter as 3/2=1 (instead
|
||||
#of 3.0/2=1.5)
|
||||
#If there is no change in the result, we return an empty dict to prevent an infinite loop due to the 3 intertwine
|
||||
#onchange. And in order to verify that there is no change in the result, we have to limit the precision of the
|
||||
#computation to 2 decimal
|
||||
liter = float(liter)
|
||||
price_per_liter = float(price_per_liter)
|
||||
amount = float(amount)
|
||||
if amount > 0 and liter > 0:
|
||||
return {'value': {'price_per_liter': amount / liter,}}
|
||||
elif amount > 0 and price_per_liter > 0:
|
||||
return {'value': {'liter': amount / price_per_liter,}}
|
||||
elif liter > 0 and price_per_liter > 0:
|
||||
return {'value': {'amount': liter * price_per_liter,}}
|
||||
return {}
|
||||
if amount > 0 and liter > 0 and round(amount/liter,2) != price_per_liter:
|
||||
return {'value': {'price_per_liter': round(amount / liter,2),}}
|
||||
elif amount > 0 and price_per_liter > 0 and round(amount/price_per_liter,2) != liter:
|
||||
return {'value': {'liter': round(amount / price_per_liter,2),}}
|
||||
elif liter > 0 and price_per_liter > 0 and round(liter*price_per_liter,2) != amount:
|
||||
return {'value': {'amount': round(liter * price_per_liter,2),}}
|
||||
else :
|
||||
return {}
|
||||
|
||||
def _get_default_service_type(self, cr, uid, context):
|
||||
try:
|
||||
|
|
|
@ -212,7 +212,6 @@ class hr_expense_expense(osv.osv):
|
|||
if analytic_journal_ids:
|
||||
account_journal.write(cr, uid, [journal.id], {'analytic_journal_id': analytic_journal_ids[0]}, context=context)
|
||||
voucher_id = voucher_obj.create(cr, uid, voucher, context=context)
|
||||
wkf_service.trg_validate(uid, 'account.voucher', voucher_id, 'proforma_voucher', cr)
|
||||
self.write(cr, uid, [exp.id], {'voucher_id': voucher_id, 'state': 'done'}, context=context)
|
||||
return True
|
||||
|
||||
|
|
|
@ -283,7 +283,14 @@ class hr_holidays(osv.osv):
|
|||
result['value']['number_of_days_temp'] = 0
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def create(self, cr, uid, values, context=None):
|
||||
""" Override to avoid automatic logging of creation """
|
||||
if context is None:
|
||||
context = {}
|
||||
context = dict(context, mail_create_nolog=True)
|
||||
return super(hr_holidays, self).create(cr, uid, values, context=context)
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
check_fnct = self.pool.get('hr.holidays.status').check_access_rights
|
||||
for holiday in self.browse(cr, uid, ids, context=context):
|
||||
|
@ -430,7 +437,7 @@ class hr_holidays(osv.osv):
|
|||
def holidays_first_validate_notificate(self, cr, uid, ids, context=None):
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
self.message_post(cr, uid, [obj.id],
|
||||
_("Request <b>approved</b>, waiting second validation."), context=context)
|
||||
_("Request approved, waiting second validation."), context=context)
|
||||
|
||||
class resource_calendar_leaves(osv.osv):
|
||||
_inherit = "resource.calendar.leaves"
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
<record id="mt_holidays_confirmed" model="mail.message.subtype">
|
||||
<field name="name">To Approve</field>
|
||||
<field name="res_model">hr.holidays</field>
|
||||
<field name="description">Request confirmed, waiting confirmation</field>
|
||||
<field name="description">Request created and waiting confirmation</field>
|
||||
</record>
|
||||
<record id="mt_holidays_approved" model="mail.message.subtype">
|
||||
<field name="name">Approved</field>
|
||||
|
|
|
@ -75,13 +75,6 @@ class mail_notification(osv.Model):
|
|||
if not cr.fetchone():
|
||||
cr.execute('CREATE INDEX mail_notification_partner_id_read_starred_message_id ON mail_notification (partner_id, read, starred, 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 get_partners_to_notify(self, cr, uid, message, context=None):
|
||||
""" Return the list of partners to notify, based on their preferences.
|
||||
|
||||
|
|
|
@ -21,10 +21,13 @@
|
|||
|
||||
import base64
|
||||
import logging
|
||||
from openerp import tools
|
||||
from urllib import urlencode
|
||||
from urlparse import urljoin
|
||||
|
||||
from openerp import tools
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.osv.orm import except_orm
|
||||
from openerp.tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
@ -158,7 +161,43 @@ class mail_mail(osv.Model):
|
|||
:param browse_record mail: mail.mail browse_record
|
||||
:param browse_record partner: specific recipient partner
|
||||
"""
|
||||
return mail.body_html
|
||||
body = mail.body_html
|
||||
# partner is a user, link to a related document (incentive to install portal)
|
||||
if partner and partner.user_ids and mail.model and mail.res_id \
|
||||
and self.check_access_rights(cr, partner.user_ids[0].id, 'read', raise_exception=False):
|
||||
related_user = partner.user_ids[0]
|
||||
try:
|
||||
self.pool.get(mail.model).check_access_rule(cr, related_user.id, [mail.res_id], 'read', context=context)
|
||||
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
|
||||
# the parameters to encode for the query and fragment part of url
|
||||
query = {'db': cr.dbname}
|
||||
fragment = {
|
||||
'login': related_user.login,
|
||||
'model': mail.model,
|
||||
'id': mail.res_id,
|
||||
}
|
||||
url = urljoin(base_url, "?%s#%s" % (urlencode(query), urlencode(fragment)))
|
||||
text = _("""<p>Access this document <a href="%s">directly in OpenERP</a></p>""") % url
|
||||
body = tools.append_content_to_html(body, ("<div><p>%s</p></div>" % text), plaintext=False)
|
||||
except except_orm, e:
|
||||
pass
|
||||
return body
|
||||
|
||||
def send_get_mail_reply_to(self, cr, uid, mail, partner=None, context=None):
|
||||
""" Return a specific ir_email body. The main purpose of this method
|
||||
is to be inherited by Portal, to add a link for signing in, in
|
||||
each notification email a partner receives.
|
||||
|
||||
:param browse_record mail: mail.mail browse_record
|
||||
:param browse_record partner: specific recipient partner
|
||||
"""
|
||||
if mail.reply_to:
|
||||
return mail.reply_to
|
||||
if not mail.model or not mail.res_id:
|
||||
return False
|
||||
if not hasattr(self.pool.get(mail.model), 'message_get_reply_to'):
|
||||
return False
|
||||
return self.pool.get(mail.model).message_get_reply_to(cr, uid, [mail.res_id], context=context)[0]
|
||||
|
||||
def send_get_email_dict(self, cr, uid, mail, partner=None, context=None):
|
||||
""" Return a dictionary for specific email values, depending on a
|
||||
|
@ -169,6 +208,7 @@ class mail_mail(osv.Model):
|
|||
"""
|
||||
body = self.send_get_mail_body(cr, uid, mail, partner=partner, context=context)
|
||||
subject = self.send_get_mail_subject(cr, uid, mail, partner=partner, context=context)
|
||||
reply_to = self.send_get_mail_reply_to(cr, uid, mail, partner=partner, context=context)
|
||||
body_alternative = tools.html2plaintext(body)
|
||||
email_to = [partner.email] if partner else tools.email_split(mail.email_to)
|
||||
return {
|
||||
|
@ -176,6 +216,7 @@ class mail_mail(osv.Model):
|
|||
'body_alternative': body_alternative,
|
||||
'subject': subject,
|
||||
'email_to': email_to,
|
||||
'reply_to': reply_to,
|
||||
}
|
||||
|
||||
def send(self, cr, uid, ids, auto_commit=False, recipient_ids=None, context=None):
|
||||
|
@ -219,7 +260,7 @@ class mail_mail(osv.Model):
|
|||
body = email.get('body'),
|
||||
body_alternative = email.get('body_alternative'),
|
||||
email_cc = tools.email_split(mail.email_cc),
|
||||
reply_to = mail.reply_to,
|
||||
reply_to = email.get('reply_to'),
|
||||
attachments = attachments,
|
||||
message_id = mail.message_id,
|
||||
references = mail.references,
|
||||
|
|
|
@ -25,6 +25,7 @@ import dateutil
|
|||
import email
|
||||
import logging
|
||||
import pytz
|
||||
import re
|
||||
import time
|
||||
import xmlrpclib
|
||||
from email.message import Message
|
||||
|
@ -39,7 +40,7 @@ _logger = logging.getLogger(__name__)
|
|||
|
||||
|
||||
def decode_header(message, header, separator=' '):
|
||||
return separator.join(map(decode, message.get_all(header, [])))
|
||||
return separator.join(map(decode, filter(None, message.get_all(header, []))))
|
||||
|
||||
|
||||
class mail_thread(osv.AbstractModel):
|
||||
|
@ -387,6 +388,18 @@ class mail_thread(osv.AbstractModel):
|
|||
return [('message_unread', '=', True)]
|
||||
return []
|
||||
|
||||
#------------------------------------------------------
|
||||
# Email specific
|
||||
#------------------------------------------------------
|
||||
|
||||
def message_get_reply_to(self, cr, uid, ids, context=None):
|
||||
if not self._inherits.get('mail.alias'):
|
||||
return [False for id in ids]
|
||||
return ["%s@%s" % (record['alias_name'], record['alias_domain'])
|
||||
if record.get('alias_domain') and record.get('alias_name')
|
||||
else False
|
||||
for record in self.read(cr, uid, ids, ['alias_name', 'alias_domain'], context=context)]
|
||||
|
||||
#------------------------------------------------------
|
||||
# Mail gateway
|
||||
#------------------------------------------------------
|
||||
|
@ -635,14 +648,14 @@ class mail_thread(osv.AbstractModel):
|
|||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
data = {}
|
||||
if isinstance(custom_values, dict):
|
||||
data = custom_values.copy()
|
||||
model = context.get('thread_model') or self._name
|
||||
model_pool = self.pool.get(model)
|
||||
fields = model_pool.fields_get(cr, uid, context=context)
|
||||
data = model_pool.default_get(cr, uid, fields, context=context)
|
||||
if 'name' in fields and not data.get('name'):
|
||||
data['name'] = msg_dict.get('subject', '')
|
||||
if custom_values and isinstance(custom_values, dict):
|
||||
data.update(custom_values)
|
||||
res_id = model_pool.create(cr, uid, data, context=context)
|
||||
return res_id
|
||||
|
||||
|
@ -807,6 +820,41 @@ class mail_thread(osv.AbstractModel):
|
|||
"now deprecated res.log.")
|
||||
self.message_post(cr, uid, [id], message, context=context)
|
||||
|
||||
def message_create_partners_from_emails(self, cr, uid, emails, context=None):
|
||||
""" Convert a list of emails into a list partner_ids and a list
|
||||
new_partner_ids. The return value is non conventional because
|
||||
it is meant to be used by the mail widget.
|
||||
|
||||
:return dict: partner_ids and new_partner_ids
|
||||
"""
|
||||
partner_obj = self.pool.get('res.partner')
|
||||
mail_message_obj = self.pool.get('mail.message')
|
||||
|
||||
partner_ids = []
|
||||
new_partner_ids = []
|
||||
for email in emails:
|
||||
m = re.search(r"((.+?)\s*<)?([^<>]+@[^<>]+)>?", email, re.IGNORECASE | re.DOTALL)
|
||||
name = m.group(2) or m.group(0)
|
||||
email = m.group(3)
|
||||
ids = partner_obj.search(cr, SUPERUSER_ID, [('email', '=', email)], context=context)
|
||||
if ids:
|
||||
partner_ids.append(ids[0])
|
||||
else:
|
||||
partner_id = partner_obj.create(cr, uid, {
|
||||
'name': name or email,
|
||||
'email': email,
|
||||
}, context=context)
|
||||
new_partner_ids.append(partner_id)
|
||||
|
||||
# link mail with this from mail to the new partner id
|
||||
message_ids = mail_message_obj.search(cr, SUPERUSER_ID, ['|', ('email_from', '=', email), ('email_from', 'ilike', '<%s>' % email), ('author_id', '=', False)], context=context)
|
||||
if message_ids:
|
||||
mail_message_obj.write(cr, SUPERUSER_ID, message_ids, {'email_from': None, 'author_id': partner_id}, context=context)
|
||||
return {
|
||||
'partner_ids': partner_ids,
|
||||
'new_partner_ids': new_partner_ids,
|
||||
}
|
||||
|
||||
def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification',
|
||||
subtype=None, parent_id=False, attachments=None, context=None, **kwargs):
|
||||
""" Post a new message in an existing thread, returning the new
|
||||
|
@ -895,7 +943,7 @@ class mail_thread(osv.AbstractModel):
|
|||
return mail_message.create(cr, uid, values, context=context)
|
||||
|
||||
def message_post_user_api(self, cr, uid, thread_id, body='', parent_id=False,
|
||||
attachment_ids=None, extra_emails=None, content_subtype='plaintext',
|
||||
attachment_ids=None, content_subtype='plaintext',
|
||||
context=None, **kwargs):
|
||||
""" Wrapper on message_post, used for user input :
|
||||
- mail gateway
|
||||
|
@ -908,26 +956,12 @@ class mail_thread(osv.AbstractModel):
|
|||
- type and subtype: comment and mail.mt_comment by default
|
||||
- attachment_ids: supposed not attached to any document; attach them
|
||||
to the related document. Should only be set by Chatter.
|
||||
- extra_email: [ 'Fabien <fpi@openerp.com>', 'al@openerp.com' ]
|
||||
"""
|
||||
partner_obj = self.pool.get('res.partner')
|
||||
mail_message_obj = self.pool.get('mail.message')
|
||||
ir_attachment = self.pool.get('ir.attachment')
|
||||
extra_emails = extra_emails or []
|
||||
|
||||
# 1.A.1: pre-process partners and incoming extra_emails
|
||||
# 1.A.1: add recipients of parent message
|
||||
partner_ids = set([])
|
||||
for email in extra_emails:
|
||||
partner_id = partner_obj.find_or_create(cr, uid, email, context=context)
|
||||
# link mail with this from mail to the new partner id
|
||||
partner_msg_ids = mail_message_obj.search(cr, SUPERUSER_ID, [('email_from', '=', email), ('author_id', '=', False)], context=context)
|
||||
if partner_id and partner_msg_ids:
|
||||
mail_message_obj.write(cr, SUPERUSER_ID, partner_msg_ids, {'email_from': None, 'author_id': partner_id}, context=context)
|
||||
partner_ids.add((4, partner_id))
|
||||
if partner_ids:
|
||||
self.message_subscribe(cr, uid, [thread_id], [item[1] for item in partner_ids], context=context)
|
||||
|
||||
# 1.A.2: add recipients of parent message
|
||||
if parent_id:
|
||||
parent_message = mail_message_obj.browse(cr, uid, parent_id, context=context)
|
||||
partner_ids |= set([(4, partner.id) for partner in parent_message.partner_ids])
|
||||
|
@ -935,8 +969,21 @@ class mail_thread(osv.AbstractModel):
|
|||
if self._name == 'mail.thread' and parent_message.author_id.id:
|
||||
partner_ids.add((4, parent_message.author_id.id))
|
||||
|
||||
# 1.A.3: add specified recipients
|
||||
partner_ids |= set(kwargs.pop('partner_ids', []))
|
||||
# 1.A.2: add specified recipients
|
||||
param_partner_ids = set()
|
||||
for item in kwargs.pop('partner_ids', []):
|
||||
if isinstance(item, (list)):
|
||||
param_partner_ids.add((item[0], item[1]))
|
||||
elif isinstance(item, (int, long)):
|
||||
param_partner_ids.add((4, item))
|
||||
else:
|
||||
param_partner_ids.add(item)
|
||||
partner_ids |= param_partner_ids
|
||||
|
||||
# 1.A.3: add parameters recipients as follower
|
||||
# TDE FIXME in 7.1: should check whether this comes from email_list or partner_ids
|
||||
if param_partner_ids and self._name != 'mail.thread':
|
||||
self.message_subscribe(cr, uid, [thread_id], [pid[1] for pid in param_partner_ids], context=context)
|
||||
|
||||
# 1.B: handle body, message_type and message_subtype
|
||||
if content_subtype == 'plaintext':
|
||||
|
|
|
@ -253,7 +253,14 @@ openerp.mail = function (session) {
|
|||
} else {
|
||||
this.avatar = mail.ChatterUtils.get_image(this.session, 'res.users', 'image_small', this.session.uid);
|
||||
}
|
||||
|
||||
if (this.author_id) {
|
||||
var email = this.author_id[1].match(/(.*)<(.*@.*)>/);
|
||||
if (!email) {
|
||||
this.author_id.push(_.str.escapeHTML(this.author_id[1]), '', this.author_id[1]);
|
||||
} else {
|
||||
this.author_id.push(_.str.escapeHTML(email[0]), _.str.trim(email[1]), email[2]);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
@ -370,6 +377,7 @@ openerp.mail = function (session) {
|
|||
this.show_compact_message = false;
|
||||
this.show_delete_attachment = true;
|
||||
this.emails_from = [];
|
||||
this.partners_from = [];
|
||||
},
|
||||
|
||||
start: function () {
|
||||
|
@ -494,43 +502,43 @@ openerp.mail = function (session) {
|
|||
this.$(".oe_msg_attachment_list").on('click', '.oe_delete', this.on_attachment_delete);
|
||||
|
||||
this.$(".oe_emails_from").on('change', 'input', this.on_checked_email_from);
|
||||
this.$(".oe_partners_from").on('change', 'input', this.on_checked_partner_from);
|
||||
},
|
||||
|
||||
on_compose_fullmail: function (default_composition_mode) {
|
||||
|
||||
var self = this;
|
||||
if(!this.do_check_attachment_upload()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (default_composition_mode == 'reply') {
|
||||
// create list of new partners
|
||||
this.check_recipient_partners().done(function (partner_ids) {
|
||||
var context = {
|
||||
'default_composition_mode': default_composition_mode,
|
||||
'default_parent_id': this.id,
|
||||
'default_body': mail.ChatterUtils.get_text2html(this.$el ? (this.$el.find('textarea:not(.oe_compact)').val() || '') : ''),
|
||||
'default_attachment_ids': this.attachment_ids,
|
||||
'default_parent_id': self.id,
|
||||
'default_body': mail.ChatterUtils.get_text2html(self.$el ? (self.$el.find('textarea:not(.oe_compact)').val() || '') : ''),
|
||||
'default_attachment_ids': self.attachment_ids,
|
||||
'default_partner_ids': partner_ids,
|
||||
};
|
||||
} else {
|
||||
var context = {
|
||||
'default_model': this.context.default_model,
|
||||
'default_res_id': this.context.default_res_id,
|
||||
'default_composition_mode': default_composition_mode,
|
||||
'default_parent_id': this.id,
|
||||
'default_body': mail.ChatterUtils.get_text2html(this.$el ? (this.$el.find('textarea:not(.oe_compact)').val() || '') : ''),
|
||||
'default_attachment_ids': this.attachment_ids,
|
||||
};
|
||||
}
|
||||
var action = {
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: 'mail.compose.message',
|
||||
view_mode: 'form',
|
||||
view_type: 'form',
|
||||
views: [[false, 'form']],
|
||||
target: 'new',
|
||||
context: context,
|
||||
};
|
||||
if (default_composition_mode != 'reply' && self.context.default_model && self.context.default_res_id) {
|
||||
context.default_model = self.context.default_model;
|
||||
context.default_res_id = self.context.default_res_id;
|
||||
}
|
||||
|
||||
var action = {
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: 'mail.compose.message',
|
||||
view_mode: 'form',
|
||||
view_type: 'form',
|
||||
views: [[false, 'form']],
|
||||
target: 'new',
|
||||
context: context,
|
||||
};
|
||||
|
||||
self.do_action(action);
|
||||
self.on_cancel();
|
||||
});
|
||||
|
||||
this.do_action(action);
|
||||
this.on_cancel();
|
||||
},
|
||||
|
||||
reinit: function() {
|
||||
|
@ -562,63 +570,65 @@ openerp.mail = function (session) {
|
|||
}
|
||||
},
|
||||
|
||||
check_recipient_partners: function (emails) {
|
||||
check_recipient_partners: function () {
|
||||
var self = this;
|
||||
var deferreds = [];
|
||||
for (var i = 0; i < emails.length; i++) {
|
||||
deferreds.push($.Deferred());
|
||||
}
|
||||
var ds_partner = new session.web.DataSetSearch(this, 'res.partner');
|
||||
_.each(emails, function (email) {
|
||||
ds_partner.call('search', [[['email', 'ilike', email]]]).then(function (partner_ids) {
|
||||
var deferred = deferreds[_.indexOf(emails, email)];
|
||||
if (!partner_ids.length) {
|
||||
var pop = new session.web.form.FormOpenPopup(this);
|
||||
pop.show_element(
|
||||
'res.partner',
|
||||
0,
|
||||
{
|
||||
'default_email': email,
|
||||
'force_email': true,
|
||||
'ref': "compound_context",
|
||||
},
|
||||
{
|
||||
title: _t("Please complete partner's informations"),
|
||||
}
|
||||
);
|
||||
pop.on('write_completed, closed', self, function () {
|
||||
deferred.resolve();
|
||||
});
|
||||
}
|
||||
else {
|
||||
var partners_from = [];
|
||||
var emails = [];
|
||||
_.each(this.emails_from, function (email_from) {
|
||||
if (email_from[1] && !_.find(emails, function (email) {return email == email_from[0][4];})) {
|
||||
emails.push(email_from[0][1]);
|
||||
}
|
||||
});
|
||||
var deferred_check = $.Deferred();
|
||||
self.parent_thread.ds_thread._model.call('message_create_partners_from_emails', [emails]).then(function (partners) {
|
||||
partners_from = _.clone(partners.partner_ids);
|
||||
var deferreds = [];
|
||||
_.each(partners.new_partner_ids, function (id) {
|
||||
var deferred = $.Deferred()
|
||||
deferreds.push(deferred);
|
||||
var pop = new session.web.form.FormOpenPopup(this);
|
||||
pop.show_element(
|
||||
'res.partner',
|
||||
id,
|
||||
{
|
||||
'force_email': true,
|
||||
'ref': "compound_context",
|
||||
},
|
||||
{
|
||||
title: _t("Please complete partner's informations"),
|
||||
}
|
||||
);
|
||||
pop.on('closed', self, function () {
|
||||
deferred.resolve();
|
||||
}
|
||||
return deferred;
|
||||
});
|
||||
partners_from.push(id);
|
||||
});
|
||||
$.when.apply( $, deferreds ).then(function () {
|
||||
deferred_check.resolve(partners_from);
|
||||
});
|
||||
});
|
||||
return $.when.apply( $, deferreds ).done();
|
||||
return deferred_check;
|
||||
},
|
||||
|
||||
on_message_post: function (event) {
|
||||
var self = this;
|
||||
if (this.do_check_attachment_upload() && (this.attachment_ids.length || this.$('textarea').val().match(/\S+/))) {
|
||||
// create list of new partners
|
||||
var extra_email = _.map(_.filter(this.emails_from, function (f) {return f[1]}), function (f) {return f[0]});
|
||||
this.check_recipient_partners(extra_email).done(function () {
|
||||
self.do_send_message_post();
|
||||
this.check_recipient_partners().done(function (partner_ids) {
|
||||
self.do_send_message_post(partner_ids);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/*do post a message and fetch the message*/
|
||||
do_send_message_post: function () {
|
||||
do_send_message_post: function (partner_ids) {
|
||||
var self = this;
|
||||
this.parent_thread.ds_thread._model.call('message_post_user_api', [this.context.default_res_id], {
|
||||
'body': this.$('textarea').val(),
|
||||
'subject': false,
|
||||
'parent_id': this.context.default_parent_id,
|
||||
'attachment_ids': _.map(this.attachment_ids, function (file) {return file.id;}),
|
||||
'extra_emails': _.map(_.filter(this.emails_from, function (f) {return f[1]}), function (f) {return f[0]}),
|
||||
'partner_ids': partner_ids,
|
||||
'context': this.parent_thread.context,
|
||||
}).done(function (message_id) {
|
||||
var thread = self.parent_thread;
|
||||
|
@ -678,26 +688,24 @@ openerp.mail = function (session) {
|
|||
_.each(this.options.root_thread.messages, function (msg) {messages.push(msg); messages.concat(msg.get_childs());});
|
||||
}
|
||||
|
||||
var emails_from = _.map(_.filter(messages,
|
||||
function (thread) {return thread.author_id && !thread.author_id[0];}),
|
||||
function (thread) {return thread.author_id[1];});
|
||||
|
||||
return _.each(emails_from, function (email_from) {
|
||||
if (!_.find(self.emails_from, function (from) {return from[0] == email_from;})) {
|
||||
self.emails_from.push([email_from, true]);
|
||||
_.each(messages, function (thread) {
|
||||
if (thread.author_id && !thread.author_id[0] &&
|
||||
!_.find(self.emails_from, function (from) {return from[0][4] == thread.author_id[4];})) {
|
||||
self.emails_from.push([thread.author_id, true]);
|
||||
}
|
||||
});
|
||||
return self.emails_from;
|
||||
},
|
||||
|
||||
on_checked_email_from: function (event) {
|
||||
var $input = $(event.target);
|
||||
var email = $input.attr("data");
|
||||
_.each(this.emails_from, function (email_from) {
|
||||
if (email_from[0] == email) {
|
||||
if (email_from[0][4] == email) {
|
||||
email_from[1] = $input.is(":checked");
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -1690,7 +1698,6 @@ openerp.mail = function (session) {
|
|||
this.defaults[key.replace(/^search_default_/, '')] = this.context[key];
|
||||
}
|
||||
}
|
||||
|
||||
this.action.params = _.extend({
|
||||
'display_indented_thread': 1,
|
||||
'show_reply_button': true,
|
||||
|
|
|
@ -221,11 +221,14 @@ openerp_mail_followers = function(session, mail) {
|
|||
var nb_subtype = 0;
|
||||
_(records).each(function (record) {nb_subtype++;});
|
||||
if (nb_subtype > 1) {
|
||||
this.$('hr').show();
|
||||
_(records).each(function (record, record_name) {
|
||||
record.name = record_name;
|
||||
record.followed = record.followed || undefined;
|
||||
$(session.web.qweb.render('mail.followers.subtype', {'record': record})).appendTo( self.$('.oe_subtype_list') );
|
||||
});
|
||||
} else {
|
||||
this.$('hr').hide();
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -132,8 +132,8 @@
|
|||
<div class="oe_emails_from">
|
||||
<t t-foreach='widget.emails_from' t-as='email_from'>
|
||||
<label title="Add them into recipients and followers">
|
||||
<input type="checkbox" t-att-checked="email_from[1] ? 'checked' : undefind" t-att-data="email_from[0]"/>
|
||||
<t t-raw="email_from[0]"/>
|
||||
<input type="checkbox" t-att-checked="email_from[1] ? 'checked' : undefind" t-att-data="email_from[0][4]"/>
|
||||
<t t-raw="email_from[0][2]"/>
|
||||
</label>
|
||||
</t>
|
||||
</div>
|
||||
|
@ -243,8 +243,8 @@
|
|||
<t t-if="widget.attachment_ids.length > 0">
|
||||
<div class="oe_msg_attachment_list"></div>
|
||||
</t>
|
||||
<a t-if="widget.author_id and widget.options.show_link and widget.author_id[0]" t-attf-href="#model=res.partner&id=#{widget.author_id[0]}"><t t-raw="widget.author_id[1]"/></a>
|
||||
<span t-if="widget.author_id and (!widget.options.show_link or !widget.author_id[0])"><t t-raw="widget.author_id[1]"/></span>
|
||||
<a t-if="widget.author_id and widget.options.show_link and widget.author_id[0]" t-attf-href="#model=res.partner&id=#{widget.author_id[0]}"><t t-raw="widget.author_id[2]"/></a>
|
||||
<span t-if="widget.author_id and (!widget.options.show_link or !widget.author_id[0])"><t t-raw="widget.author_id[2]"/></span>
|
||||
<span class='oe_subtle'>•</span>
|
||||
<span t-att-title="widget.date"><t t-raw="widget.timerelative"/></span>
|
||||
<span t-if="!widget.options.readonly" class='oe_subtle'>•</span>
|
||||
|
|
|
@ -76,8 +76,6 @@ class invite_wizard(osv.osv_memory):
|
|||
'subject': 'Invitation to follow %s' % document.name_get()[0][1],
|
||||
'body_html': '%s' % wizard.message,
|
||||
'auto_delete': True,
|
||||
'res_id': False,
|
||||
'model': False,
|
||||
}, context=context)
|
||||
mail_mail.send(cr, uid, [mail_id], recipient_ids=[follower_id], context=context)
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<field name="res_id" invisible="1"/>
|
||||
<field name="partner_ids" widget="many2many_tags_email"
|
||||
placeholder="Add contacts to notify..."
|
||||
context="{'force_email':True}"/>
|
||||
context="{'force_email':True, 'show_email':True}"/>
|
||||
<field name="message"/>
|
||||
</group>
|
||||
<footer>
|
||||
|
|
|
@ -168,6 +168,7 @@ class mail_compose_message(osv.TransientModel):
|
|||
reply_subject = "%s %s" % (re_prefix, reply_subject)
|
||||
# get partner_ids from original message
|
||||
partner_ids = [partner.id for partner in message_data.partner_ids] if message_data.partner_ids else []
|
||||
partner_ids += context.get('default_partner_ids', [])
|
||||
|
||||
# update the result
|
||||
result = {
|
||||
|
|
|
@ -74,11 +74,9 @@
|
|||
}
|
||||
|
||||
.etherpad_readonly ul, .etherpad_readonly ol {
|
||||
margin-before: 1em;
|
||||
margin-after: 1em;
|
||||
margin-start: 0px;
|
||||
margin-end: 0px;
|
||||
padding-start: 40px !important;
|
||||
margin: 0;
|
||||
margin-left: 1.5em;
|
||||
padding: 0;
|
||||
}
|
||||
.etherpad_readonly ul li{
|
||||
list-style-type: disc;
|
||||
|
@ -92,8 +90,8 @@
|
|||
|
||||
.etherpad_readonly{
|
||||
font-family: arial, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
font-size: 15px;
|
||||
line-height: 19px;
|
||||
}
|
||||
|
||||
.openerp .oe_form_nomargin .etherpad_readonly{
|
||||
|
@ -109,3 +107,12 @@
|
|||
.etherpad_readonly ol ol ol ol ol ol li{ list-style-type: lower-roman !important; }
|
||||
.etherpad_readonly ol ol ol ol ol ol ol li{ list-style-type: decimal !important; }
|
||||
.etherpad_readonly ol ol ol ol ol ol ol ol li{ list-style-type: lower-latin !important; }
|
||||
.etherpad_readonly ul li { list-style-type: disc !important; }
|
||||
.etherpad_readonly ul ul li { list-style-type: circle !important; }
|
||||
.etherpad_readonly ul ul ul li { list-style-type: square !important; }
|
||||
.etherpad_readonly ul ul ul ul li { list-style-type: disc !important; }
|
||||
.etherpad_readonly ul ul ul ul ul li { list-style-type: circle !important; }
|
||||
.etherpad_readonly ul ul ul ul ul ul li { list-style-type: square !important; }
|
||||
.etherpad_readonly ul ul ul ul ul ul ul li { list-style-type: disc !important; }
|
||||
.etherpad_readonly ul ul ul ul ul ul ul ul li { list-style-type: circle !important; }
|
||||
.etherpad_readonly ul ul ul ul ul ul ul ul ul li { list-style-type: square !important; }
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import osv
|
||||
from openerp.osv.orm import except_orm
|
||||
from openerp.tools import append_content_to_html
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
@ -35,10 +36,21 @@ class mail_mail(osv.Model):
|
|||
:param partner: browse_record of the specific recipient partner
|
||||
:return: the resulting body_html
|
||||
"""
|
||||
body = super(mail_mail, self).send_get_mail_body(cr, uid, mail, partner, context=context)
|
||||
partner_obj = self.pool.get('res.partner')
|
||||
body = mail.body_html
|
||||
if partner:
|
||||
context = dict(context or {}, signup_valid=True)
|
||||
partner = self.pool.get('res.partner').browse(cr, SUPERUSER_ID, partner.id, context=context)
|
||||
text = _("""Access your personal documents through <a href="%s">our Customer Portal</a>""") % partner.signup_url
|
||||
contex_signup = dict(context or {}, signup_valid=True)
|
||||
partner = partner_obj.browse(cr, SUPERUSER_ID, partner.id, context=contex_signup)
|
||||
text = _("""<p>Access your messages and personal documents through <a href="%s">our Customer Portal</a></p>""") % partner.signup_url
|
||||
# partner is an user: add a link to the document if read access
|
||||
if partner.user_ids and mail.model and mail.res_id \
|
||||
and self.check_access_rights(cr, partner.user_ids[0].id, 'read', raise_exception=False):
|
||||
related_user = partner.user_ids[0]
|
||||
try:
|
||||
self.pool.get(mail.model).check_access_rule(cr, related_user.id, [mail.res_id], 'read', context=context)
|
||||
url = partner_obj._get_signup_url_for_action(cr, related_user.id, [partner.id], action='', res_id=mail.res_id, model=mail.model, context=context)[partner.id]
|
||||
text = _("""<p>Access this document <a href="%s">directly in OpenERP</a></p>""") % url
|
||||
except except_orm, e:
|
||||
pass
|
||||
body = append_content_to_html(body, ("<div><p>%s</p></div>" % text), plaintext=False)
|
||||
return body
|
||||
|
|
|
@ -44,8 +44,9 @@ openerp.portal_anonymous = function(instance) {
|
|||
start: function() {
|
||||
var self = this;
|
||||
return $.when(this._super()).then(function() {
|
||||
var params = $.deparam($.param.querystring());
|
||||
var dblist = self.db_list || [];
|
||||
if (!self.session.session_is_valid() && dblist.length === 1 && _.isEmpty(self.params)) {
|
||||
if (!self.session.session_is_valid() && dblist.length === 1 && (!params.token || !params.login)) {
|
||||
self.remember_credentials = false;
|
||||
// XXX get login/pass from server (via a rpc call) ?
|
||||
return self.do_login(dblist[0], 'anonymous', 'anonymous');
|
||||
|
|
|
@ -1166,6 +1166,11 @@ class task(base_stage, osv.osv):
|
|||
# Mail gateway
|
||||
# ---------------------------------------------------
|
||||
|
||||
def message_get_reply_to(self, cr, uid, ids, context=None):
|
||||
""" Override to get the reply_to of the parent project. """
|
||||
return [task.project_id.message_get_reply_to()[0] if task.project_id else False
|
||||
for task in self.browse(cr, uid, ids, context=context)]
|
||||
|
||||
def message_new(self, cr, uid, msg, custom_values=None, context=None):
|
||||
""" Override to updates the document according to the email. """
|
||||
if custom_values is None: custom_values = {}
|
||||
|
@ -1198,7 +1203,7 @@ class task(base_stage, osv.osv):
|
|||
act = 'do_%s' % res.group(2).lower()
|
||||
if act:
|
||||
getattr(self,act)(cr, uid, ids, context=context)
|
||||
return super(task,self).message_update(cr, uid, msg, update_vals=update_vals, context=context)
|
||||
return super(task,self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context)
|
||||
|
||||
def project_task_reevaluate(self, cr, uid, ids, context=None):
|
||||
if self.pool.get('res.users').has_group(cr, uid, 'project.group_time_work_estimation_tasks'):
|
||||
|
|
|
@ -481,6 +481,11 @@ class project_issue(base_stage, osv.osv):
|
|||
# Mail gateway
|
||||
# -------------------------------------------------------
|
||||
|
||||
def message_get_reply_to(self, cr, uid, ids, context=None):
|
||||
""" Override to get the reply_to of the parent project. """
|
||||
return [issue.project_id.message_get_reply_to()[0] if issue.project_id else False
|
||||
for issue in self.browse(cr, uid, ids, context=context)]
|
||||
|
||||
def message_new(self, cr, uid, msg, custom_values=None, context=None):
|
||||
""" Overrides mail_thread message_new that is called by the mailgateway
|
||||
through message_process.
|
||||
|
|
|
@ -31,7 +31,7 @@ Collects web application usage with Google Analytics.
|
|||
""",
|
||||
'author': 'OpenERP SA',
|
||||
'website': 'http://openerp.com',
|
||||
'depends': ['web', 'auth_signup'],
|
||||
'depends': ['web'],
|
||||
'data': [],
|
||||
'installable': True,
|
||||
'active': False,
|
||||
|
|
|
@ -3,12 +3,13 @@ var _gaq = _gaq || []; // asynchronous stack used by google analytics
|
|||
|
||||
openerp.web_analytics = function(instance) {
|
||||
|
||||
/** The Google Analytics Module inserts the Google Analytics JS Snippet
|
||||
* at the top of the page, and sends to google an url each time the
|
||||
* openerp url is changed.
|
||||
* The pushes of the urls is made by triggering the 'state_pushed' event in the
|
||||
* web_client.do_push_state() method which is responsible of changing the openerp current url
|
||||
*/
|
||||
/*
|
||||
* The Web Analytics Module inserts the Google Analytics JS Snippet
|
||||
* at the top of the page, and sends to google an url each time the
|
||||
* openerp url is changed.
|
||||
* The pushes of the urls is made by triggering the 'state_pushed' event in the
|
||||
* web_client.do_push_state() method which is responsible of changing the openerp current url
|
||||
*/
|
||||
|
||||
// Google Analytics Code snippet
|
||||
(function() {
|
||||
|
@ -20,138 +21,219 @@ openerp.web_analytics = function(instance) {
|
|||
s.parentNode.insertBefore(ga,s);
|
||||
})();
|
||||
|
||||
instance.web_analytics.Tracker = instance.web.Class.extend({
|
||||
/*
|
||||
* This method initializes the tracker
|
||||
*/
|
||||
init: function() {
|
||||
/* Comment this lines when going on production, only used for testing on localhost
|
||||
_gaq.push(['_setAccount', 'UA-35793871-1']);
|
||||
_gaq.push(['_setDomainName', 'none']);
|
||||
*/
|
||||
|
||||
var init_tracker = function() {
|
||||
// initialize tracker
|
||||
_gaq.push(['_setAccount', 'UA-7333765-1']);
|
||||
//_gaq.push(['_setAccount', 'UA-36797757-1']); // Debug code
|
||||
_gaq.push(['_setDomainName', 'none']); // Change for the real domain
|
||||
|
||||
// Track user types
|
||||
if (instance.session.uid !== 1) {
|
||||
if ((/\.demo.openerp.com/).test(instance.session.server)) {
|
||||
_gaq.push(['_setCustomVar', 1, 'User Type', 'Demo User', 1]);
|
||||
/* Uncomment this lines when going on production */
|
||||
_gaq.push(['_setAccount', 'UA-7333765-1']);
|
||||
_gaq.push(['_setDomainName', '.openerp.com']); // Allow multi-domain
|
||||
/**/
|
||||
},
|
||||
/*
|
||||
* This method MUST be overriden by saas_demo and saas_trial in order to
|
||||
* set the correct user type. By default, the user connected is local to the DB.
|
||||
*/
|
||||
_get_user_type: function() {
|
||||
return 'Local User';
|
||||
},
|
||||
/*
|
||||
* This method gets the user access level, to be used as CV in GA
|
||||
*/
|
||||
_get_user_access_level: function() {
|
||||
if (instance.session.uid === 1) {
|
||||
return 'Admin User';
|
||||
// Make the difference between portal users and anonymous users
|
||||
} else if (instance.session.username.indexOf('@') !== -1) {
|
||||
if (instance.session.username.indexOf('anonymous') === -1) {
|
||||
return 'Portal User';
|
||||
} else {
|
||||
return 'Anonymous User';
|
||||
}
|
||||
} else if (instance.session.username.indexOf('anonymous') !== -1) {
|
||||
return 'Anonymous User';
|
||||
} else {
|
||||
_gaq.push(['_setCustomVar', 1, 'User Type', 'Normal User', 1]);
|
||||
return 'Normal User';
|
||||
}
|
||||
} else {
|
||||
_gaq.push(['_setCustomVar', 1, 'User Type', 'Admin User', 1]);
|
||||
}
|
||||
},
|
||||
/*
|
||||
* This method contains the initialization of all user-related custom variables
|
||||
* stored in GA. Also other modules can override it to add new custom variables
|
||||
*/
|
||||
initialize_custom: function() {
|
||||
// Track User Access Level, Custom Variable 4 in GA with visitor level scope
|
||||
// Values: 'Admin User', 'Normal User', 'Portal User', 'Anonymous User'
|
||||
_gaq.push(['_setCustomVar', 4, 'User Access Level', this.user_access_level, 1]);
|
||||
|
||||
// Track object usage
|
||||
_gaq.push(['_setCustomVar', 2, 'Object', 'no_model', 3]);
|
||||
// Tack view usage
|
||||
_gaq.push(['_setCustomVar', 3, 'View Type', 'default', 3]);
|
||||
// Track User Type Conversion, Custom Variable 3 in GA with session level scope
|
||||
// Values: 'Visitor', 'Demo', 'Online Trial', 'Online Paying', 'Local User'
|
||||
_gaq.push(['_setCustomVar', 1, 'User Type Conversion', this._get_user_type(), 2]);
|
||||
|
||||
_gaq.push(['_trackPageview']);
|
||||
};
|
||||
|
||||
var on_state_pushed = function(state) {
|
||||
// Track only pages corresponding to a 'normal' view of OpenERP, views
|
||||
// related to client actions are tracked by the action manager
|
||||
if (state.model && state.view_type) {
|
||||
// Track object usage
|
||||
_gaq.push(['_setCustomVar', 2, 'Object', state.model, 3]);
|
||||
// Tack view usage
|
||||
_gaq.push(['_setCustomVar', 3, 'View Type', state.view_type, 3]);
|
||||
// Track the page
|
||||
var url = instance.web_analytics.generateUrl({'model': state.model, 'view_type': state.view_type});
|
||||
_gaq.push(['_trackPageview', url]);
|
||||
}
|
||||
};
|
||||
|
||||
var include_tracker = function() {
|
||||
// include the tracker into views and managers
|
||||
|
||||
// Track the events related with the creation and the modification of records
|
||||
instance.web.FormView = instance.web.FormView.extend({
|
||||
init: function(parent, dataset, view_id, options) {
|
||||
this._super.apply(this, arguments);
|
||||
var self = this;
|
||||
this.on('record_created', self, function(r) {
|
||||
var url = instance.web_analytics.generateUrl({'model': this.model, 'view_type': 'form'});
|
||||
_gaq.push(['_trackEvent', this.model, 'on_button_create_save', url]);
|
||||
return instance.session.rpc("/web/webclient/version_info", {})
|
||||
.done(function(res) {
|
||||
_gaq.push(['_setCustomVar', 5, 'Version', res.server_version, 3]);
|
||||
_gaq.push(['_trackPageview']);
|
||||
return;
|
||||
});
|
||||
this.on('record_saved', self, function(r) {
|
||||
var url = instance.web_analytics.generateUrl({'model': this.model, 'view_type': 'form'});
|
||||
_gaq.push(['_trackEvent', this.model, 'on_button_edit_save', url]);
|
||||
},
|
||||
/*
|
||||
* Method called in order to send _trackEvent to GA
|
||||
*/
|
||||
_push_event: function(options) {
|
||||
_gaq.push(['_trackEvent',
|
||||
options.category,
|
||||
options.action,
|
||||
options.label,
|
||||
options.value,
|
||||
options.noninteraction
|
||||
]);
|
||||
},
|
||||
/*
|
||||
* Method called in order to send ecommerce transactions to GA
|
||||
*/
|
||||
_push_ecommerce: function(trans_data, item_list) {
|
||||
_gaq.push(['_addTrans',
|
||||
trans_data.order_id,
|
||||
trans_data.store_name,
|
||||
trans_data.total,
|
||||
trans_data.tax,
|
||||
trans_data.shipping,
|
||||
trans_data.city,
|
||||
trans_data.state,
|
||||
trans_data.country,
|
||||
]);
|
||||
_.each(item_list, function(item) {
|
||||
_gaq.push(['_addItem',
|
||||
item.order_id,
|
||||
item.sku,
|
||||
item.name,
|
||||
item.category,
|
||||
item.price,
|
||||
item.quantity,
|
||||
]);
|
||||
});
|
||||
_gaq.push(['_trackTrans']);
|
||||
},
|
||||
/*
|
||||
* This method contains the initialization of the object and view type
|
||||
* as an event in GA.
|
||||
*/
|
||||
on_state_pushed: function(state) {
|
||||
// Track only pages corresponding to a 'normal' view of OpenERP, views
|
||||
// related to client actions are tracked by the action manager
|
||||
if (state.model && state.view_type) {
|
||||
// Track the page
|
||||
var url = instance.web_analytics.generateUrl({'model': state.model, 'view_type': state.view_type});
|
||||
this._push_event({
|
||||
'category': state.model,
|
||||
'action': state.view_type,
|
||||
'label': url,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Track client actions
|
||||
instance.web.ActionManager.include({
|
||||
ir_actions_client: function (action, options) {
|
||||
var url = instance.web_analytics.generateUrl({'action': action.tag});
|
||||
_gaq.push(['_trackPageview', url]);
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
// Track button events
|
||||
instance.web.View.include({
|
||||
do_execute_action: function(action_data, dataset, record_id, on_closed) {
|
||||
console.log(action_data);
|
||||
var category = this.model || dataset.model || '';
|
||||
var action;
|
||||
if (action_data.name && _.isNaN(action_data.name-0)) {
|
||||
action = action_data.name;
|
||||
} else {
|
||||
action = action_data.string || action_data.special || '';
|
||||
},
|
||||
/*
|
||||
* This method includes the tracker into views and managers. It can be overriden
|
||||
* by other modules in order to extend tracking functionalities
|
||||
*/
|
||||
include_tracker: function() {
|
||||
var t = this;
|
||||
// Track the events related with the creation and the modification of records,
|
||||
// the view type is always form
|
||||
instance.web.FormView.include({
|
||||
init: function(parent, dataset, view_id, options) {
|
||||
this._super.apply(this, arguments);
|
||||
var self = this;
|
||||
this.on('record_created', self, function(r) {
|
||||
var url = instance.web_analytics.generateUrl({'model': r.model, 'view_type': 'form'});
|
||||
t._push_event({
|
||||
'category': r.model,
|
||||
'action': 'form',
|
||||
'label': url,
|
||||
});
|
||||
});
|
||||
this.on('record_saved', self, function(r) {
|
||||
var url = instance.web_analytics.generateUrl({'model': r.model, 'view_type': 'form'});
|
||||
t._push_event({
|
||||
'category': r.model,
|
||||
'action': 'form',
|
||||
'label': url,
|
||||
});
|
||||
});
|
||||
}
|
||||
var label = instance.web_analytics.generateUrl({'model': category, 'view_type': this.view_type});
|
||||
_gaq.push(['_trackEvent', category, action, label]);
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// Track error events
|
||||
instance.web.CrashManager.include({
|
||||
show_error: function(error) {
|
||||
var hash = window.location.hash;
|
||||
var params = $.deparam(hash.substr(hash.indexOf('#')+1));
|
||||
var options = {};
|
||||
if (params.model && params.view_type) {
|
||||
options = {'model': params.model, 'view_type': params.view_type};
|
||||
} else {
|
||||
options = {'action': params.action};
|
||||
}
|
||||
var label = instance.web_analytics.generateUrl(options);
|
||||
if (error.code) {
|
||||
_gaq.push(['_trackEvent', error.message, error.data.fault_code, label, ,true]);
|
||||
} else {
|
||||
_gaq.push(['_trackEvent', error.type, error.data.debug, label, ,true]);
|
||||
}
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
};
|
||||
// Track client actions
|
||||
instance.web.ActionManager.include({
|
||||
ir_actions_client: function (action, options) {
|
||||
var url = instance.web_analytics.generateUrl({'action': action.tag});
|
||||
var category = action.res_model || action.type;
|
||||
t._push_event({
|
||||
'category': action.res_model || action.type,
|
||||
'action': action.name || action.tag,
|
||||
'label': url,
|
||||
});
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
// Track button events
|
||||
instance.web.View.include({
|
||||
do_execute_action: function(action_data, dataset, record_id, on_closed) {
|
||||
var category = this.model || dataset.model || '';
|
||||
var action;
|
||||
if (action_data.name && _.isNaN(action_data.name-0)) {
|
||||
action = action_data.name;
|
||||
} else {
|
||||
action = action_data.string || action_data.special || '';
|
||||
}
|
||||
var url = instance.web_analytics.generateUrl({'model': category, 'view_type': this.view_type});
|
||||
t._push_event({
|
||||
'category': category,
|
||||
'action': action,
|
||||
'label': url,
|
||||
});
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
if (instance.client instanceof instance.web.WebClient) { // not for embedded clients
|
||||
init_tracker();
|
||||
|
||||
// Set the account and domain to start tracking
|
||||
instance.client.on('state_pushed', this, on_state_pushed);
|
||||
|
||||
include_tracker();
|
||||
} else if (!instance.client) {
|
||||
// client does not already exists, we are in monodb mode
|
||||
|
||||
instance.web.WebClient.include({
|
||||
init: function() {
|
||||
init_tracker();
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
start: function() {
|
||||
var self = this;
|
||||
return this._super.apply(this, arguments).done(function() {
|
||||
self.on('state_pushed', self, on_state_pushed);
|
||||
include_tracker();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
// Track error events
|
||||
instance.web.CrashManager.include({
|
||||
show_error: function(error) {
|
||||
var hash = window.location.hash;
|
||||
var params = $.deparam(hash.substr(hash.indexOf('#')+1));
|
||||
var options = {};
|
||||
if (params.model && params.view_type) {
|
||||
options = {'model': params.model, 'view_type': params.view_type};
|
||||
} else {
|
||||
options = {'action': params.action};
|
||||
}
|
||||
var url = instance.web_analytics.generateUrl(options);
|
||||
if (error.code) {
|
||||
t._push_event({
|
||||
'category': error.message,
|
||||
'action': error.data.fault_code,
|
||||
'label': url,
|
||||
'noninteraction': true,
|
||||
});
|
||||
} else {
|
||||
t._push_event({
|
||||
'category': error.type,
|
||||
'action': error.data.debug,
|
||||
'label': url,
|
||||
'noninteraction': true,
|
||||
});
|
||||
}
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// utility functions
|
||||
|
@ -165,4 +247,37 @@ openerp.web_analytics = function(instance) {
|
|||
return url;
|
||||
};
|
||||
|
||||
instance.web_analytics.setupTracker = function(wc) {
|
||||
var t = wc.tracker;
|
||||
return $.when(t._get_user_access_level()).then(function(r) {
|
||||
t.user_access_level = r;
|
||||
t.initialize_custom().then(function() {
|
||||
wc.on('state_pushed', t, t.on_state_pushed);
|
||||
t.include_tracker();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Set correctly the tracker in the current instance
|
||||
if (instance.client instanceof instance.web.WebClient) { // not for embedded clients
|
||||
instance.webclient.tracker = new instance.web_analytics.Tracker();
|
||||
instance.web_analytics.setupTracker(instance.webclient);
|
||||
} else if (!instance.client) {
|
||||
// client does not already exists, we are in monodb mode
|
||||
instance.web.WebClient.include({
|
||||
start: function() {
|
||||
var d = this._super.apply(this, arguments);
|
||||
this.tracker = new instance.web_analytics.Tracker();
|
||||
return d;
|
||||
},
|
||||
show_application: function() {
|
||||
var self = this;
|
||||
$.when(this.subscribe_deferred).then(function() {
|
||||
instance.web_analytics.setupTracker(self);
|
||||
});
|
||||
this._super();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue