[MERGE]sync with trunk

bzr revid: sgo@tinyerp.com-20130319045706-imgv7yyhvp508j8l
This commit is contained in:
sgo@tinyerp.com 2013-03-19 10:27:06 +05:30
commit c45280eb2d
43 changed files with 297 additions and 334 deletions

View File

@ -30,7 +30,6 @@
</record>
<record id="account_payment_term_line_15days" model="account.payment.term.line">
<field name="name">15 Days</field>
<field name="value">balance</field>
<field eval="15" name="days"/>
<field eval="0" name="days2"/>
@ -48,7 +47,6 @@
</record>
<record id="account_payment_term_line_net" model="account.payment.term.line">
<field name="name">30 Net Days</field>
<field name="value">balance</field>
<field eval="30" name="days"/>
<field eval="0" name="days2"/>

View File

@ -135,7 +135,6 @@
<field name="note">30 Days End of Month</field>
</record>
<record id="account_payment_term_line" model="account.payment.term.line">
<field name="name">30 Days End of Month</field>
<field name="value">balance</field>
<field eval="30" name="days"/>
<field eval="-1" name="days2"/>
@ -147,16 +146,13 @@
<field name="note">30% Advance End 30 Days</field>
</record>
<record id="account_payment_term_line_advance1" model="account.payment.term.line">
<field name="name">30% Advance</field>
<field name="value">procent</field>
<field eval="3" name="sequence"/>
<field eval="0.300000" name="value_amount"/>
<field eval="0" name="days"/>
<field eval="0" name="days2"/>
<field eval="account_payment_term_advance" name="payment_id"/>
</record>
<record id="account_payment_term_line_advance2" model="account.payment.term.line">
<field name="name">Remaining Balance</field>
<field name="value">balance</field>
<field eval="30" name="days"/>
<field eval="-1" name="days2"/>

View File

@ -276,7 +276,7 @@
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('invoice_id','=',False)]</field>
<field name="context">{'search_default_to_invoice': 1, 'search_default_sales': 1}</field>
<field name="context">{'search_default_to_invoice': 1}</field>
<field name="search_view_id" ref="account.view_account_analytic_line_filter"/>
<field name="help" type="html">
<p>

View File

@ -53,7 +53,6 @@ have a new option to import payment orders as bank statement lines.
'account_payment_view.xml',
'account_payment_workflow.xml',
'account_payment_sequence.xml',
'account_invoice_view.xml',
'account_payment_report.xml',
],
'demo': ['account_payment_demo.xml'],

View File

@ -19,9 +19,8 @@
#
##############################################################################
from datetime import datetime
from openerp.tools.translate import _
from openerp.osv import fields, osv
from openerp.osv import osv
class Invoice(osv.osv):
_inherit = 'account.invoice'
@ -43,28 +42,6 @@ class Invoice(osv.osv):
raise osv.except_osv(_('Error!'), _("You cannot cancel an invoice which has already been imported in a payment order. Remove it from the following payment order : %s."%(payment_order_name)))
return super(Invoice, self).action_cancel(cr, uid, ids, context=context)
def _amount_to_pay(self, cursor, user, ids, name, args, context=None):
'''Return the amount still to pay regarding all the payment orders'''
if not ids:
return {}
res = {}
for invoice in self.browse(cursor, user, ids, context=context):
res[invoice.id] = 0.0
if invoice.move_id:
for line in invoice.move_id.line_id:
if not line.date_maturity or \
datetime.strptime(line.date_maturity, '%Y-%m-%d') \
< datetime.today():
res[invoice.id] += line.amount_to_pay
return res
_columns = {
'amount_to_pay': fields.function(_amount_to_pay,
type='float', string='Amount to be paid',
help='The amount which should be paid at the current date\n' \
'minus the amount which is already in payment order'),
}
Invoice()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="invoice_supplier_form" model="ir.ui.view">
<field name="name">account.invoice.supplier.form.inherit</field>
<field name="model">account.invoice</field>
<field name="inherit_id" ref="account.invoice_supplier_form"/>
<field name="arch" type="xml">
<field name="partner_bank_id" position="before">
<field name="amount_to_pay"/>
</field>
</field>
</record>
</data>
</openerp>

View File

@ -19,65 +19,12 @@
#
##############################################################################
from operator import itemgetter
from openerp.osv import fields, osv
from openerp.osv import osv
from openerp.tools.translate import _
class account_move_line(osv.osv):
_inherit = "account.move.line"
def amount_to_pay(self, cr, uid, ids, name, arg=None, context=None):
""" Return the amount still to pay regarding all the payemnt orders
(excepting cancelled orders)"""
if not ids:
return {}
cr.execute("""SELECT ml.id,
CASE WHEN ml.amount_currency < 0
THEN - ml.amount_currency
ELSE ml.credit
END -
(SELECT coalesce(sum(amount_currency),0)
FROM payment_line pl
INNER JOIN payment_order po
ON (pl.order_id = po.id)
WHERE move_line_id = ml.id
AND po.state != 'cancel') AS amount
FROM account_move_line ml
WHERE id IN %s""", (tuple(ids),))
r = dict(cr.fetchall())
return r
def _to_pay_search(self, cr, uid, obj, name, args, context=None):
if not args:
return []
line_obj = self.pool.get('account.move.line')
query = line_obj._query_get(cr, uid, context={})
where = ' and '.join(map(lambda x: '''(SELECT
CASE WHEN l.amount_currency < 0
THEN - l.amount_currency
ELSE l.credit
END - coalesce(sum(pl.amount_currency), 0)
FROM payment_line pl
INNER JOIN payment_order po ON (pl.order_id = po.id)
WHERE move_line_id = l.id
AND po.state != 'cancel'
) %(operator)s %%s ''' % {'operator': x[1]}, args))
sql_args = tuple(map(itemgetter(2), args))
cr.execute(('''SELECT id
FROM account_move_line l
WHERE account_id IN (select id
FROM account_account
WHERE type=%s AND active)
AND reconcile_id IS null
AND credit > 0
AND ''' + where + ' and ' + query), ('payable',)+sql_args )
res = cr.fetchall()
if not res:
return [('id', '=', '0')]
return [('id', 'in', map(lambda x:x[0], res))]
def line2bank(self, cr, uid, ids, payment_type=None, context=None):
"""
Try to return for each Ledger Posting line a corresponding bank
@ -110,11 +57,6 @@ class account_move_line(osv.osv):
raise osv.except_osv(_('Error!'), _('There is no partner defined on the entry line.'))
return line2bank
_columns = {
'amount_to_pay': fields.function(amount_to_pay,
type='float', string='Amount to pay', fnct_search=_to_pay_search),
}
account_move_line()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -352,7 +352,7 @@ class payment_line(osv.osv):
if move_line_id:
line = move_line_obj.browse(cr, uid, move_line_id, context=context)
data['amount_currency'] = line.amount_to_pay
data['amount_currency'] = line.amount_residual_currency
res = self.onchange_amount(cr, uid, ids, data['amount_currency'], currency,
company_currency, context)

View File

@ -2,29 +2,6 @@
<openerp>
<data>
<!-- View used in the wizard -->
<record id="view_move_line_form" model="ir.ui.view">
<field name="name">account.move.line.form.inherit</field>
<field name="model">account.move.line</field>
<field name="inherit_id" ref="account.view_move_line_form"/>
<field name="arch" type="xml">
<field name="reconcile_partial_id" position="after">
<field name="amount_to_pay"/>
</field>
</field>
</record>
<!-- <record model="ir.ui.view" id="view_move_line_tree_wiz">
<field name="name">account.move.line.tree</field>
<field name="model">account.move.line</field>
<field name="inherit_id" ref="account.view_move_line_tree"/>
<field name="arch" type="xml">
<field name="reconcile" position="after">
<field name="amount_to_pay"/>
</field>
</field>
</record> -->
<menuitem id="menu_main_payment" name="Payment" parent="account.menu_finance" sequence="7"/>
<record id="view_payment_mode_search" model="ir.ui.view">
@ -116,7 +93,7 @@
<notebook>
<page string="Payment">
<group col="4">
<field name="move_line_id" on_change="onchange_move_line(move_line_id,parent.mode,parent.date_prefered,parent.date_scheduled,currency,company_currency)" domain="[('reconcile_id','=', False), ('credit', '>',0),('amount_to_pay','>',0)] "/>
<field name="move_line_id" on_change="onchange_move_line(move_line_id,parent.mode,parent.date_prefered,parent.date_scheduled,currency,company_currency)" domain="[('reconcile_id','=', False), ('credit', '>',0),('amount_residual','>',0)] "/>
<separator colspan="4" string="Transaction Information"/>
<field name="date"/>
<label for="amount_currency" groups="base.group_multi_currency"/>
@ -242,7 +219,7 @@
<page string="Payment">
<group col="4">
<field name="order_id"/>
<field name="move_line_id" on_change="onchange_move_line(move_line_id, False, currency, company_currency)" domain="[('reconcile_id','=', False), ('credit', '>',0),('amount_to_pay','>',0)]"/>
<field name="move_line_id" on_change="onchange_move_line(move_line_id, False, currency, company_currency)" domain="[('reconcile_id','=', False), ('credit', '>',0),('amount_residual','>',0)]"/>
<separator colspan="4" string="Transaction Information"/>
<field name="date"/>
<label for="amount_currency" groups="base.group_multi_currency"/>

View File

@ -82,7 +82,7 @@ class payment_order_create(osv.osv_memory):
date_to_pay = payment.date_scheduled
payment_obj.create(cr, uid,{
'move_line_id': line.id,
'amount_currency': line.amount_to_pay,
'amount_currency': line.amount_residual_currency,
'bank_id': line2bank.get(line.id),
'order_id': payment.id,
'partner_id': line.partner_id and line.partner_id.id or False,
@ -102,7 +102,7 @@ class payment_order_create(osv.osv_memory):
# payment = self.pool.get('payment.order').browse(cr, uid, context['active_id'], context=context)
# Search for move line to pay:
domain = [('reconcile_id', '=', False), ('account_id.type', '=', 'payable'), ('amount_to_pay', '>', 0)]
domain = [('reconcile_id', '=', False), ('account_id.type', '=', 'payable'), ('amount_residual', '>', 0)]
domain = domain + ['|', ('date_maturity', '<=', search_due_date), ('date_maturity', '=', False)]
line_ids = line_obj.search(cr, uid, domain, context=context)
context.update({'line_ids': line_ids})

View File

@ -1,25 +1,15 @@
.login .pane {
width: 260px;
height: 175px;
}
.login .pane input[name='openid_url'] {
.oe_login .oe_login_pane input[name='openid_url'] {
background: #fff url(../img/login-bg.gif) no-repeat 1px;
padding-left: 20px;
}
.auth_choice {
position: static;
display: none;
.openerp .oe_login .openid_providers {
text-align: center;
}
.openid_providers {
padding: 0;
list-style: none;
float: right;
.openerp .oe_login .openid_providers ul {
display: inline-block;
}
.openid_providers li {
.openerp .oe_login .openid_providers ul li {
display: block;
float: left;
margin: 0 1px 3px 2px;
@ -29,7 +19,6 @@
display: block;
width: 24px;
height: 24px;
border: 1px solid #ddd;
background: #fff url(../img/openid_16.png) no-repeat 50%;
text-indent: -9999px;
overflow: hidden;
@ -37,20 +26,16 @@
}
.openid_providers a.selected {
border-color: #9A0404;
background-color: #DC5F59;
}
.openid_providers a[title="Password"] { background-image: url(../img/textfield_key.png); }
.openid_providers a[title="AOL"] { background-image: url(../img/aol.png); }
.openid_providers a[title="ClaimID"] { background-image: url(../img/claimid.png); }
.openid_providers a[title="Google"] { background-image: url(../img/googlefav.png); }
.openid_providers a[title="Google Apps"] { background-image: url(../img/marketplace.gif); }
.openid_providers a[title="MyOpenID"] { background-image: url(../img/myopenid.png); }
.openid_providers a[title="VeriSign"] { background-image: url(../img/verisign.png); }
.openid_providers a[title="Yahoo!"] { background-image: url(../img/yahoo.png); }
.openid_providers a[title="Launchpad"] { background-image: url(../img/launchpad.png); }
.openid_providers a[data-provider="Password"] { background-image: url(../img/textfield_key.png); }
.openid_providers a[data-provider="AOL"] { background-image: url(../img/aol.png); }
.openid_providers a[data-provider="ClaimID"] { background-image: url(../img/claimid.png); }
.openid_providers a[data-provider="Google"] { background-image: url(../img/googlefav.png); }
.openid_providers a[data-provider="Google Apps"] { background-image: url(../img/marketplace.gif); }
.openid_providers a[data-provider="MyOpenID"] { background-image: url(../img/myopenid.png); }
.openid_providers a[data-provider="VeriSign"] { background-image: url(../img/verisign.png); }
.openid_providers a[data-provider="Yahoo!"] { background-image: url(../img/yahoo.png); }
.openid_providers a[data-provider="Launchpad"] { background-image: url(../img/launchpad.png); }
li.auth_choice.selected {
display: table-row;
}

View File

@ -14,6 +14,16 @@ instance.web.Login = instance.web.Login.extend({
self.$openid_selected_input = $();
self.$openid_selected_provider = null;
// Hook auth_signup events. noop if module is not installed.
self.on('change:login_mode', self, function() {
var mode = self.get('login_mode') || 'default';
if (mode !== 'default') {
return;
}
self.do_openid_select(self.$openid_selected_button, self.$openid_selected_provider, true);
});
var openIdProvider = null;
if (self.has_local_storage && self.remember_credentials) {
@ -21,12 +31,10 @@ instance.web.Login = instance.web.Login.extend({
}
if (openIdProvider) {
$openid_selected_provider = openIdProvider;
self.$openid_selected_provider = openIdProvider;
self.do_openid_select('a[href="#' + openIdProvider + '"]', openIdProvider, true);
if (self.has_local_storage && self.remember_credentials) {
self.$openid_selected_input.find('input').val(localStorage.getItem('openid-login'));
}
self.$openid_selected_input.find('input').val(localStorage.getItem('openid-login') || '');
}
else {
self.do_openid_select('a[data-url=""]', 'login,password', true);
@ -49,11 +57,12 @@ instance.web.Login = instance.web.Login.extend({
do_openid_select: function (button, provider, noautosubmit) {
var self = this;
self.$('li[data-provider]').hide();
self.$openid_selected_button.add(self.$openid_selected_input).removeClass('selected');
self.$openid_selected_button = self.$el.find(button).addClass('selected');
var input = _(provider.split(',')).map(function(p) { return 'li[data-provider="'+p+'"]'; }).join(',');
self.$openid_selected_input = self.$el.find(input).addClass('selected');
self.$openid_selected_input = self.$el.find(input).show();
self.$openid_selected_input.find('input:first').focus();
self.$openid_selected_provider = (self.$openid_selected_button.attr('href') || '').substr(1);
@ -62,7 +71,7 @@ instance.web.Login = instance.web.Login.extend({
localStorage.setItem('openid-provider', self.$openid_selected_provider);
}
if (!noautosubmit && self.$openid_selected_input.length == 0) {
if (!noautosubmit && self.$openid_selected_input.length === 0) {
self.$el.find('form').submit();
}

View File

@ -4,44 +4,53 @@
<t t-extend="Login">
<t t-jquery=".oe_login .oe_login_logo" t-operation="after">
<ul class="openid_providers oe_semantic_html_override">
<li><a href="#login,password" title="Password" data-url="" id="btn_password">Password</a></li>
<li><a href="#google" title="Google" data-url="https://www.google.com/accounts/o8/id">Google</a></li>
<li><a href="#googleapps" title="Google Apps" data-url="https://www.google.com/accounts/o8/site-xrds?hd={id}">Google</a></li>
<li><a href="#launchpad" title="Launchpad" data-url="https://launchpad.net/~{id}">Launchpad</a></li>
<li><a href="#openid_url" title="OpenID" data-url="{id}">OpenID</a></li>
</ul>
<div class="openid_providers" data-modes="default openid"><ul>
<li><a href="#login,password" data-provider='Password' title="Password" data-url="" id="btn_password">Password</a></li>
<li><a href="#google" data-provider='Google' title="Google" data-url="https://www.google.com/accounts/o8/id">Google</a></li>
<li><a href="#googleapps" data-provider='Google Apps' title="Google Apps" data-url="https://www.google.com/accounts/o8/site-xrds?hd={id}">Google</a></li>
<li><a href="#launchpad" data-provider='Launchpad' title="Launchpad" data-url="https://launchpad.net/~{id}">Launchpad</a></li>
<li><a href="#openid_url" data-provider='OpenID' title="OpenID" data-url="{id}">OpenID</a></li>
</ul></div>
</t>
</t>
<t t-extend="Login">
<t t-jquery=".oe_login .oe_login_pane form ul li:nth-child(4)" t-operation="after">
<li>
<t t-jquery=".oe_login .oe_login_pane form ul li:last-child()" t-operation="before">
<li data-modes="openid" data-provider='googleapps'>
Google Apps Domain
</li>
<li>
<li data-modes="openid" data-provider='googleapps'>
<input type="text" name="googleapps" />
</li>
<li>
<li data-modes="openid" data-provider='launchpad'>
Username
</li>
<li>
<li data-modes="openid" data-provider='launchpad'>
<input type="text" name="launchpad" />
</li>
<li>
<li data-modes="openid" data-provider='openid_url'>
OpenID URL
</li>
<li>
<li data-modes="openid" data-provider='openid_url'>
<input type="text" name="openid_url" />
</li>
</t>
</t>
<t t-extend="Login">
<t t-jquery=".oe_login .oe_login_pane form ul li:has(input)">
<t t-jquery=".oe_login .oe_login_pane form ul li:has(input[name=password])">
this.each(function() {
var $i = $(this);
$i.add($i.prev()).attr('data-provider', 'password');
});
</t>
<t t-jquery=".oe_login .oe_login_pane form ul li:has(input[name=login])">
this.each(function() {
var $i = $(this),
dp = $i.find('input').attr('name');
$i.add($i.prev()).attr('class', 'auth_choice').attr('data-provider', dp);
dp = $i.find('input').attr('name'),
$p = $i.prev();
// $p may not be the correct label when auth_signup is installed.
while(($p.attr('data-modes') || 'default') !== 'default') { $p = $p.prev(); }
$i.add($p).attr('data-provider', dp);
});
</t>
</t>

View File

@ -161,14 +161,12 @@ class res_users(osv.Model):
def _get_state(self, cr, uid, ids, name, arg, context=None):
res = {}
for user in self.browse(cr, uid, ids, context):
res[user.id] = ('reset' if user.signup_valid else
'active' if user.login_date else
'new')
res[user.id] = ('active' if user.login_date else 'new')
return res
_columns = {
'state': fields.function(_get_state, string='Status', type='selection',
selection=[('new', 'New'), ('active', 'Active'), ('reset', 'Resetting Password')]),
selection=[('new', 'Never Connected'), ('active', 'Activated')]),
}
def signup(self, cr, uid, values, token=None, context=None):
@ -270,16 +268,7 @@ class res_users(osv.Model):
if mail_state and mail_state['state'] == 'exception':
raise self.pool.get('res.config.settings').get_config_warning(cr, _("Cannot send email: no outgoing email server configured.\nYou can configure it under %(menu:base_setup.menu_general_configuration)s."), context)
else:
return {
'type': 'ir.actions.client',
'name': '_(Server Notification)',
'tag': 'action_notify',
'params': {
'title': 'Mail Sent to: %s' % user.name,
'text': 'You can reset the password by yourself using this <a href=%s>link</a>' % user.partner_id.signup_url,
'sticky': True,
}
}
return True
def create(self, cr, uid, values, context=None):
# overridden to automatically invite user to sign up

View File

@ -17,13 +17,25 @@
<header>
<field name="state" widget="statusbar"/>
</header>
<div class="oe_form_box_info oe_text_center" attrs="{'invisible': [('signup_valid', '!=', True)]}">
<p attrs="{'invisible': [('state', '!=', 'active')]}">
<b>A password reset has been requested for this user. An email containing the following link has been sent:</b>
</p>
<p attrs="{'invisible': [('state', '!=', 'new')]}">
<b>An invitation email containing the following subscription link has been sent:</b>
</p>
<p><field class="oe_inline" name="signup_url" widget="url"/></p>
<field name="signup_valid" invisible="1"/>
</div>
</xpath>
<!-- add Reset Password button -->
<xpath expr="//div[@class='oe_right oe_button_box']" position="replace">
<div class="oe_right oe_button_box">
<xpath expr="//div[@class='oe_right oe_button_box']//button" position="replace">
<button string="Send Reset Password Instructions"
type="object" name="action_reset_password" />
</div>
type="object" name="action_reset_password"
attrs="{'invisible': [('state', '!=', 'active')]}"/>
<button string="Send an Invitation Email"
type="object" name="action_reset_password" context="{'create_user': 1}"
attrs="{'invisible': [('state', '!=', 'new')]}"/>
</xpath>
</field>
</record>

View File

@ -56,7 +56,7 @@ openerp.auth_signup = function(instance) {
self.rpc("/auth_signup/get_config", {dbname: dbname}).done(function(result) {
self.signup_enabled = result.signup;
self.reset_password_enabled = result.reset_password;
if (self.$("form input[name=login]").val()){
if (!self.signup_enabled || self.$("form input[name=login]").val()){
self.set('login_mode', 'default');
} else {
self.set('login_mode', 'signup');

View File

@ -12,6 +12,9 @@
<li data-modes="default">Username</li>
<li data-modes="signup reset">Username (Email)</li>
</t>
<t t-jquery="form ul:first li:has(input[name=login], input[name=password]), form ul:first li:contains('Password')">
this.attr('data-modes', 'default signup reset');
</t>
<t t-jquery="form ul:first li:has(input[name=password])" t-operation="after">
<li data-modes="signup reset">Confirm Password</li>
<li data-modes="signup reset"><input name="confirm_password" type="password"/></li>

View File

@ -104,9 +104,9 @@
</header>
<sheet>
<div class="oe_right oe_button_box" name="buttons">
<button type="action"
name="%(act_crm_opportunity_crm_phonecall_new)d"
string="Phone Calls"/>
<button string="Schedule/Log Call"
name="%(opportunity2phonecall_act)d"
type="action"/>
</div>
<div class="oe_title">
<label for="name" class="oe_edit_only"/>
@ -151,7 +151,8 @@
-->
</group>
<group>
<field name="user_id"/>
<field name="user_id"
context="{'default_groups_ref': ['base.group_user', 'base.group_sale_salesman_all_leads']}"/>
<label for="section_id"/>
<div>
<field name="section_id"/>
@ -426,7 +427,7 @@
</group>
<group>
<field name="user_id"/>
<field name="user_id" context="{'default_groups_ref': ['base.group_user', 'base.group_sale_salesman_all_leads']}"/>
<label for="section_id"/>
<div>
<field name="section_id" widget="selection"/>
@ -436,7 +437,7 @@
<group>
<field name="categ_ids"
string="Categories" widget="many2many_tags"
context = "{'object_name': 'crm.lead'}"
context="{'object_name': 'crm.lead'}"
domain="[('object_id.model', '=', 'crm.lead')]"/>
</group>

View File

@ -77,6 +77,7 @@
<field name="inherit_id" ref="base.view_partner_tree"/>
<field name="arch" type="xml">
<field name="user_id" position="after">
<field name="date_review_next"/>
<field name="grade_id"/>
<field name="activation"/>
</field>
@ -104,17 +105,19 @@
<field name="arch" type="xml">
<xpath expr="//notebook[last()]" position="inside">
<page string="Geo Localization">
<group colspan="2" col="2">
<separator string="Partner Activation" colspan="2"/>
<field name="grade_id" widget="selection"/>
<field name="activation" widget="selection"/>
<field name="partner_weight"/>
</group>
<group colspan="2" col="2">
<separator string="Partner Review" colspan="2"/>
<field name="date_review"/>
<field name="date_review_next"/>
<field name="date_partnership"/>
<group>
<group>
<separator string="Partner Activation" colspan="2"/>
<field name="grade_id" widget="selection"/>
<field name="activation" widget="selection"/>
<field name="partner_weight"/>
</group>
<group>
<separator string="Partner Review" colspan="2"/>
<field name="date_review"/>
<field name="date_review_next"/>
<field name="date_partnership"/>
</group>
</group>
<group colspan="2" col="2">
<separator string="Geo Localization" colspan="2"/>

View File

@ -49,7 +49,8 @@
</group>
<group>
<field name="company_id" groups="base.group_multi_company" on_change="onchange_company(company_id)"/>
<field name="user_id" on_change="onchange_user(user_id)" string="Related User"/>
<field name="user_id" on_change="onchange_user(user_id)" string="Related User"
context="{'default_groups_ref': ['base.group_user']}"/>
</group>
</group>
<field name="notes" placeholder="Other Information ..." colspan="4"/>

View File

@ -374,6 +374,7 @@
<field name="model">hr.holidays.status</field>
<field name="arch" type="xml">
<form string="Leave Type" version="7.0">
<sheet string="Leave Type">
<group col="4">
<field name="name"/>
<field name="categ_id"/>
@ -392,6 +393,7 @@
</group>
</page>
</notebook>
</sheet>
</form>
</field>
</record>

View File

@ -107,7 +107,8 @@
<field name="type_id" placeholder="Degree"/>
</group>
<group>
<field name="user_id"/>
<field name="user_id"
context="{'default_groups_ref': ['base.group_user', 'base.group_hr_manager']}"/>
<label for="title_action"/>
<div>
<field name="date_action"/>
@ -341,6 +342,7 @@
<field name="model">hr.recruitment.stage</field>
<field name="arch" type="xml">
<form string="Stage" version="7.0">
<sheet>
<group string="Stage Definition">
<group>
<field name="name"/>
@ -354,6 +356,7 @@
</group>
<separator string="Requirements"/>
<field name="requirements"/>
</sheet>
</form>
</field>
</record>
@ -399,10 +402,12 @@
<field name="model">hr.recruitment.degree</field>
<field name="arch" type="xml">
<form string="Degree" version="7.0">
<sheet>
<group>
<field name="name"/>
<field name="sequence" groups="base.group_no_one"/>
</group>
</sheet>
</form>
</field>
</record>
@ -436,8 +441,12 @@
<field name="name">hr.recruitment.source.form</field>
<field name="model">hr.recruitment.source</field>
<field name="arch" type="xml">
<form string="Sources of Applicants">
<field name="name"/>
<form string="Sources of Applicants" version="7.0">
<sheet>
<group>
<field name="name"/>
</group>
</sheet>
</form>
</field>
</record>

View File

@ -8,7 +8,8 @@
<field name="arch" type="xml">
<tree editable="top" string="Timesheet Activities">
<field name="date" on_change="on_change_date(date)"/>
<field name="user_id" on_change="on_change_user_id(user_id)" required="1" options='{"no_open": True}'/>
<field name="user_id" on_change="on_change_user_id(user_id)" required="1" options='{"no_open": True}'
context="{'default_groups_ref': [base.group_user']}"/>
<field name="name"/>
<field domain="[('type','=','normal'),('use_timesheets','=',1)]" name="account_id" context="{'default_use_timesheets': 1, 'default_type': 'contract'}"/>
<field name="unit_amount" string="Duration" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" sum="Total time" widget="float_time"/>
@ -29,7 +30,8 @@
<group>
<group>
<field name="name"/>
<field name="user_id" on_change="on_change_user_id(user_id)" required="1"/>
<field name="user_id" on_change="on_change_user_id(user_id)" required="1"
context="{'default_groups_ref': [base.group_user']}"/>
</group>
<group>
<field name="date" on_change="on_change_date(date)"/>

View File

@ -213,6 +213,7 @@ class hr_timesheet_sheet(osv.osv):
def onchange_employee_id(self, cr, uid, ids, employee_id, context=None):
department_id = False
user_id = False
if employee_id:
empl_id = self.pool.get('hr.employee').browse(cr, uid, employee_id, context=context)
department_id = empl_id.department_id.id

View File

@ -324,7 +324,8 @@
<sheet>
<group>
<group>
<field name='user_id'/>
<field name='user_id'
context="{'default_groups_ref': ['base.group_user', 'lunch.group_lunch_user']}"/>
</group>
<group>
<field name='date'/>
@ -409,7 +410,8 @@
<form string="cashmove form" version="7.0">
<sheet>
<group>
<field name="user_id"/>
<field name="user_id"
context="{'default_groups_ref': ['base.group_user', 'lunch.group_lunch_user']}"/>
<field name="date"/>
<field name="amount"/>
</group>

View File

@ -20,6 +20,8 @@
##############################################################################
from openerp.osv import osv, fields
from openerp import tools, SUPERUSER_ID
from openerp.tools.translate import _
from openerp.tools.mail import plaintext2html
class mail_followers(osv.Model):
""" mail_followers holds the data related to the follow mechanism inside
@ -105,6 +107,43 @@ class mail_notification(osv.Model):
notify_pids.append(partner.id)
return notify_pids
def get_signature_footer(self, cr, uid, user_id, res_model=None, res_id=None, context=None):
""" Format a standard footer for notification emails (such as pushed messages
notification or invite emails).
Format:
<p>--<br />
Administrator
</p>
<div>
<small>Send by <a ...>Your Company</a> using <a ...>OpenERP</a>.</small> OR
<small>Send by Administrator using <a ...>OpenERP</a>.</small>
</div>
"""
footer = ""
if not user_id:
return footer
# add user signature
user = self.pool.get("res.users").browse(cr, SUPERUSER_ID, [user_id], context=context)[0]
if user.signature:
signature = plaintext2html(user.signature)
else:
signature = "--<br />%s" % user.name
footer = tools.append_content_to_html(footer, signature, plaintext=False, container_tag='p')
# add company signature
if user.company_id:
company = user.company_id.website and "<a style='color:inherit' href='%s'>%s</a>" % (user.company_id.website, user.company_id.name) or user.company_id.name
else:
company = user.name
signature_company = _('<small>Send by %(company)s using %(openerp)s.</small>' % {
'company': company,
'openerp': "<a style='color:inherit' href='https://www.openerp.com/'>OpenERP</a>"
})
footer = tools.append_content_to_html(footer, signature_company, plaintext=False, container_tag='div')
return footer
def _notify(self, cr, uid, msg_id, partners_to_notify=None, context=None):
""" Send by email the notification depending on the user preferences
@ -141,14 +180,11 @@ class mail_notification(osv.Model):
# TDE FIXME: commented, to be improved in a future branch
# quote_context = self.pool.get('mail.message').message_quote_context(cr, uid, msg_id, context=context)
mail_mail = self.pool.get('mail.mail')
# add signature
body_html = msg.body
# if quote_context:
# body_html = tools.append_content_to_html(body_html, quote_context, plaintext=False)
signature = msg.author_id and msg.author_id.user_ids and msg.author_id.user_ids[0].signature or ''
if signature:
body_html = tools.append_content_to_html(body_html, signature, plaintext=True, container_tag='div')
user_id = msg.author_id and msg.author_id.user_ids and msg.author_id.user_ids[0] and msg.author_id.user_ids[0].id or None
signature_company = self.get_signature_footer(cr, uid, user_id, res_model=msg.model, res_id=msg.res_id, context=context)
body_html = tools.append_content_to_html(body_html, signature_company, plaintext=False, container_tag='div')
# email_from: partner-user alias or partner email or mail.message email_from
if msg.author_id and msg.author_id.user_ids and msg.author_id.user_ids[0].alias_domain and msg.author_id.user_ids[0].alias_name:
@ -164,6 +200,7 @@ class mail_notification(osv.Model):
'body_html': body_html,
'email_from': email_from,
}
mail_mail = self.pool.get('mail.mail')
email_notif_id = mail_mail.create(cr, uid, mail_values, context=context)
try:
return mail_mail.send(cr, uid, [email_notif_id], recipient_ids=notify_partner_ids, context=context)

View File

@ -160,15 +160,12 @@ class mail_mail(osv.Model):
return 'Re: %s' % (mail.record_name)
return mail.subject
def send_get_mail_body(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
def send_get_mail_body_footer(self, cr, uid, mail, partner=None, context=None):
""" Return a specific footer for the ir_email body. The main purpose of this method
is to be inherited by Portal, to add modify the 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
"""
body = mail.body_html
body_footer = ""
# 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):
@ -184,10 +181,23 @@ class mail_mail(osv.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)
body_footer = _("""<small>Access this document <a style='color:inherit' href="%s">directly in OpenERP</a></small>""") % url
except except_orm, e:
pass
return body_footer
def send_get_mail_body(self, cr, uid, mail, partner=None, context=None):
""" Return a specific ir_email body. The main purpose of this method
is to be inherited to add custom content depending on some module.
:param browse_record mail: mail.mail browse_record
:param browse_record partner: specific recipient partner
"""
body = mail.body_html
# add footer
body_footer = self.send_get_mail_body_footer(cr, uid, mail, partner=partner, context=context)
body = tools.append_content_to_html(body, body_footer, plaintext=False, container_tag='div')
return body
def send_get_mail_reply_to(self, cr, uid, mail, partner=None, context=None):

View File

@ -23,7 +23,7 @@
<field name="inherit_id" ref="base.view_users_form"/>
<field name="arch" type="xml">
<data>
<field name="email" position="before">
<field name="signature" position="before">
<field name="notification_email_send"/>
</field>
<field name="email" position="after">

View File

@ -561,6 +561,14 @@
text-align: center;
width:100%;
}
.openerp .oe_followers button.oe_invite{
margin: 5px 0;
padding: 2px 8px;
font-size: 12px;
text-shadow: none;
width: 100%;
}
.openerp .oe_followers button.oe_invite,
.openerp .oe_followers button.oe_follower.oe_following{
color: white;
background-color: #3465A4;
@ -614,9 +622,6 @@
margin-top: 12px;
margin-bottom: 4px;
}
.openerp .oe_followers .oe_invite{
float: right;
}
.openerp .oe_followers .oe_partner {
height: 32px;
overflow: hidden;

View File

@ -220,23 +220,16 @@ openerp_mail_followers = function(session, mail) {
/** Display subtypes: {'name': default, followed} */
display_subtypes:function (data, id) {
var self = this;
var subtype_list_ul = this.$('.oe_subtype_list');
var records = [];
var nb_subtype = 0;
subtype_list_ul.empty();
if (data[id]) {
records = data[id].message_subtype_data;
}
_(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();
var $list = this.$('.oe_subtype_list');
$list.empty().hide();
var records = data[this.view.datarecord.id || this.view.dataset.ids[0]].message_subtype_data;
_(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') );
});
if (_.size(records) > 1) {
$list.show();
}
},

View File

@ -17,10 +17,9 @@
</t>
<div class="oe_subtype_list"></div>
</div>
<hr size="2"></hr>
<div class='oe_follower_title_box'>
<h4 class='oe_follower_title'>Followers</h4>
<a t-if="widget.view_is_editable" href='#' class="oe_invite">Add others</a>
<button t-if="widget.view_is_editable" href='#' class="oe_invite">Invite people</button>
</div>
<div class="oe_follower_list"></div>
</div>

View File

@ -31,18 +31,18 @@ class test_invite(TestMailBase):
# Do: create a mail_wizard_invite, validate it
self._init_mock_build_email()
context = {'default_res_model': 'mail.group', 'default_res_id': self.group_pigs_id}
mail_invite_id = mail_invite.create(cr, self.user_raoul_id, {'partner_ids': [(4, self.partner_bert_id)]}, context)
mail_invite_id = mail_invite.create(cr, self.user_raoul_id, {'partner_ids': [(4, self.partner_bert_id)], 'send_mail': True}, context)
mail_invite.add_followers(cr, self.user_raoul_id, [mail_invite_id], {'default_model': 'mail.group', 'default_res_id': 0})
# Test: Pigs followers should contain Admin, Bert
self.group_pigs.refresh()
follower_ids = [follower.id for follower in self.group_pigs.message_follower_ids]
self.assertEqual(set(follower_ids), set([self.partner_admin_id, self.partner_bert_id]), 'Pigs followers after invite is incorrect')
self.assertEqual(set(follower_ids), set([self.partner_admin_id, self.partner_bert_id]), 'invite: Pigs followers after invite is incorrect')
# Test: (pretend to) send email and check subject, body
self.assertEqual(len(self._build_email_kwargs_list), 1, 'sent email number incorrect, should be only for Bert')
for sent_email in self._build_email_kwargs_list:
self.assertEqual(sent_email.get('subject'), 'Invitation to follow Pigs',
'subject of invitation email is incorrect')
self.assertTrue('You have been invited to follow Pigs' in sent_email.get('body'),
'body of invitation email is incorrect')
self.assertEqual(sent_email.get('subject'), 'Invitation to follow Discussion group: Pigs',
'invite: subject of invitation email is incorrect')
self.assertIn('Raoul Grosbedon invited you to follow Discussion group document: Pigs', sent_email.get('body'),
'invite: body of invitation email is incorrect')

View File

@ -84,23 +84,6 @@ Sylvie
class test_mail(TestMailBase):
def _mock_send_get_mail_body(self, *args, **kwargs):
# def _send_get_mail_body(self, cr, uid, mail, partner=None, context=None)
body = append_content_to_html(args[2].body_html, kwargs.get('partner').name if kwargs.get('partner') else 'No specific partner', plaintext=False)
return body
def setUp(self):
super(test_mail, self).setUp()
# Mock send_get_mail_body to test its functionality without other addons override
self._send_get_mail_body = self.registry('mail.mail').send_get_mail_body
self.registry('mail.mail').send_get_mail_body = self._mock_send_get_mail_body
def tearDown(self):
# Remove mocks
self.registry('mail.mail').send_get_mail_body = self._send_get_mail_body
super(test_mail, self).tearDown()
def test_00_message_process(self):
""" Testing incoming emails processing. """
cr, uid, user_raoul = self.cr, self.uid, self.user_raoul
@ -341,11 +324,15 @@ class test_mail(TestMailBase):
_subject = 'Pigs'
_mail_subject = 'Re: %s' % (group_pigs.name)
_body1 = '<p>Pigs rules</p>'
_mail_body1 = '<p>Pigs rules</p>\n<div><p>Raoul</p></div>\n'
_mail_bodyalt1 = 'Pigs rules\nRaoul\n'
_mail_body1 = '<p>Pigs rules</p>'
_mail_signature1 = '<p>Raoul</p>'
_mail_bodyalt1 = 'Pigs rules\n'
_mail_signaturealt1 = '\nRaoul\n'
_body2 = '<html>Pigs rules</html>'
_mail_body2 = '<div><p>Pigs rules</p></div>\n<div><p>Raoul</p></div>'
_mail_bodyalt2 = 'Pigs rules\nRaoul'
_mail_body2 = '<div><p>Pigs rules</p></div>'
_mail_signature2 = '<p>Raoul</p>'
_mail_bodyalt2 = 'Pigs rules\n'
_mail_signaturealt2 = '\nRaoul\n'
_attachments = [('First', 'My first attachment'), ('Second', 'My second attachment')]
# ----------------------------------------
@ -366,12 +353,14 @@ class test_mail(TestMailBase):
self.assertEqual(len(sent_emails), 2, 'sent_email number of sent emails incorrect')
for sent_email in sent_emails:
self.assertEqual(sent_email['subject'], _subject, 'sent_email subject incorrect')
self.assertTrue(sent_email['body'] in [_mail_body1 + '\nBert Tartopoils\n', _mail_body1 + '\nAdministrator\n'],
'sent_email body incorrect')
self.assertIn(_mail_body1, sent_email['body'], 'sent_email body incorrect')
self.assertIn(_mail_signature1, sent_email['body'], 'sent_email body incorrect (no signature)')
self.assertIn("OpenERP", sent_email['body'], 'sent_email body incorrect (no OpenERP company)')
# the html2plaintext uses etree or beautiful soup, so the result may be slighly different
# depending if you have installed beautiful soup.
self.assertTrue(sent_email['body_alternative'] in [_mail_bodyalt1 + '\nBert Tartopoils\n', _mail_bodyalt1 + '\nAdministrator\n'],
'sent_email body_alternative is incorrect')
self.assertIn(_mail_bodyalt1, sent_email['body_alternative'], 'sent_email body_alternative is incorrect')
self.assertIn(_mail_signaturealt1, sent_email['body_alternative'], 'sent_email body_alternative is incorrect (no signature)')
self.assertIn("OpenERP", sent_email['body_alternative'], 'sent_email body incorrect (no OpenERP company)')
# Test: mail_message: notified_partner_ids = group followers
message_pids = set([partner.id for partner in message1.notified_partner_ids])
test_pids = set([self.partner_admin_id, p_b_id, p_c_id])
@ -405,7 +394,12 @@ class test_mail(TestMailBase):
for sent_email in sent_emails:
self.assertEqual(sent_email['subject'], _mail_subject, 'sent_email subject incorrect')
self.assertIn(_mail_body2, sent_email['body'], 'sent_email body incorrect')
self.assertIn(_mail_bodyalt2, sent_email['body_alternative'], 'sent_email body_alternative incorrect')
self.assertIn(_mail_signature2, sent_email['body'], 'sent_email body incorrect (no signature)')
self.assertIn("OpenERP", sent_email['body'], 'sent_email body incorrect (no OpenERP company)')
# body_alternative
self.assertIn(_mail_bodyalt2, sent_email['body_alternative'], 'sent_email body_alternative is incorrect')
self.assertIn(_mail_signaturealt2, sent_email['body_alternative'], 'sent_email body_alternative is incorrect (no signature)')
self.assertIn("OpenERP", sent_email['body'], 'sent_email body incorrect (no OpenERP company)')
# Test: mail_message: notified_partner_ids = group followers
message_pids = set([partner.id for partner in message2.notified_partner_ids])
test_pids = set([self.partner_admin_id, p_b_id, p_c_id, p_d_id])

View File

@ -32,12 +32,19 @@ class invite_wizard(osv.osv_memory):
def default_get(self, cr, uid, fields, context=None):
result = super(invite_wizard, self).default_get(cr, uid, fields, context=context)
if 'message' in fields and result.get('res_model') and result.get('res_id'):
document_name = self.pool.get(result.get('res_model')).name_get(cr, uid, [result.get('res_id')], context=context)[0][1]
message = _('<div>You have been invited to follow %s.</div>' % document_name)
user_name = self.pool.get('res.users').name_get(cr, uid, [uid], context=context)[0][1]
model = result.get('res_model')
res_id = result.get('res_id')
if 'message' in fields and model and res_id:
ir_model = self.pool.get('ir.model')
model_ids = ir_model.search(cr, uid, [('model', '=', self.pool.get(model)._name)], context=context)
model_name = ir_model.name_get(cr, uid, model_ids, context=context)[0][1]
document_name = self.pool.get(model).name_get(cr, uid, [res_id], context=context)[0][1]
message = _('<div><p>Hello,</p><p>%s invited you to follow %s document: %s.<p></div>') % (user_name, model_name, document_name)
result['message'] = message
elif 'message' in fields:
result['message'] = _('<div>You have been invited to follow a new document.</div>')
result['message'] = _('<div><p>Hello,</p><p>%s invited you to follow a new document.</p></div>') % user_name
return result
_columns = {
@ -46,8 +53,12 @@ class invite_wizard(osv.osv_memory):
help='Model of the followed resource'),
'res_id': fields.integer('Related Document ID', select=1,
help='Id of the followed resource'),
'partner_ids': fields.many2many('res.partner', string='Partners'),
'partner_ids': fields.many2many('res.partner', string='Recipients',
help="List of partners that will be added as follower of the current document."),
'message': fields.html('Message'),
'send_mail': fields.boolean('Send Email',
help="If checked, the partners will receive an email warning they have been "
"added in the document's followers."),
}
def add_followers(self, cr, uid, ids, context=None):
@ -59,21 +70,25 @@ class invite_wizard(osv.osv_memory):
new_follower_ids = [p.id for p in wizard.partner_ids if p not in document.message_follower_ids]
model_obj.message_subscribe(cr, uid, [wizard.res_id], new_follower_ids, context=context)
# send an email
if wizard.message:
ir_model = self.pool.get('ir.model')
model_ids = ir_model.search(cr, uid, [('model', '=', model_obj._name)], context=context)
model_name = ir_model.name_get(cr, uid, model_ids, context=context)[0][1]
# send an email if option checked and if a message exists (do not send void emails)
if wizard.send_mail and wizard.message:
# add signature
user_id = self.pool.get("res.users").read(cr, uid, [uid], fields=["signature"], context=context)[0]
signature = user_id and user_id["signature"] or ''
if signature:
wizard.message = tools.append_content_to_html(wizard.message, signature, plaintext=True, container_tag='div')
# FIXME 8.0: use notification_email_send, send a wall message and let mail handle email notification + message box
signature_company = self.pool.get('mail.notification').get_signature_footer(cr, uid, user_id=uid, res_model=wizard.res_model, res_id=wizard.res_id, context=context)
wizard.message = tools.append_content_to_html(wizard.message, signature_company, plaintext=False, container_tag='div')
# send mail to new followers
for follower_id in new_follower_ids:
mail_mail = self.pool.get('mail.mail')
# the invite wizard should create a private message not related to any object -> no model, no res_id
mail_id = mail_mail.create(cr, uid, {
'model': wizard.res_model,
'res_id': wizard.res_id,
'subject': 'Invitation to follow %s' % document.name_get()[0][1],
'subject': _('Invitation to follow %s: %s') % (model_name, document.name_get()[0][1]),
'body_html': '%s' % wizard.message,
'auto_delete': True,
}, context=context)

View File

@ -14,7 +14,8 @@
<field name="partner_ids" widget="many2many_tags_email"
placeholder="Add contacts to notify..."
context="{'force_email':True, 'show_email':True}"/>
<field name="message"/>
<field name="send_mail"/>
<field name="message" attrs="{'invisible': [('send_mail','!=',True)]}"/>
</group>
<footer>
<button string="Add Followers"

View File

@ -22,7 +22,6 @@
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 _
@ -30,18 +29,17 @@ class mail_mail(osv.Model):
""" Update of mail_mail class, to add the signin URL to notifications. """
_inherit = 'mail.mail'
def send_get_mail_body(self, cr, uid, mail, partner=None, context=None):
def send_get_mail_body_footer(self, cr, uid, mail, partner=None, context=None):
""" add a signin link inside the body of a mail.mail
:param mail: mail.mail browse_record
:param partner: browse_record of the specific recipient partner
:return: the resulting body_html
"""
partner_obj = self.pool.get('res.partner')
body = mail.body_html
if partner:
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
body_footer = _("""<small>Access your messages and documents through <a style='color:inherit' href="%s">our Customer Portal</a></small>""") % 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):
@ -49,8 +47,9 @@ class mail_mail(osv.Model):
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
text = _("""<small>Access this document <a style='color:inherit' href="%s">directly in OpenERP</a></small>""") % url
except except_orm, e:
pass
body = append_content_to_html(body, ("<div><p>%s</p></div>" % text), plaintext=False)
return body
return body_footer
else:
return super(mail_mail, self).send_get_mail_body_footer(cr, uid, mail, partner=partner, context=context)

View File

@ -106,7 +106,7 @@ class test_portal(TestMailBase):
# Do: create a mail_wizard_invite, validate it
self._init_mock_build_email()
context = {'default_res_model': 'mail.group', 'default_res_id': self.group_pigs_id}
mail_invite_id = mail_invite.create(cr, uid, {'partner_ids': [(4, partner_carine_id)]}, context)
mail_invite_id = mail_invite.create(cr, uid, {'partner_ids': [(4, partner_carine_id)], 'send_mail': True}, context)
mail_invite.add_followers(cr, uid, [mail_invite_id])
# Test: Pigs followers should contain Admin and Bert
@ -124,9 +124,9 @@ class test_portal(TestMailBase):
# Test: (pretend to) send email and check subject, body
self.assertEqual(len(self._build_email_kwargs_list), 1, 'sent email number incorrect, should be only for Bert')
for sent_email in self._build_email_kwargs_list:
self.assertEqual(sent_email.get('subject'), 'Invitation to follow Pigs',
'subject of invitation email is incorrect')
self.assertTrue('You have been invited to follow Pigs' in sent_email.get('body'),
'body of invitation email is incorrect')
self.assertEqual(sent_email.get('subject'), 'Invitation to follow Discussion group: Pigs',
'invite: subject of invitation email is incorrect')
self.assertIn('Administrator invited you to follow Discussion group document: Pigs', sent_email.get('body'),
'invite: body of invitation email is incorrect')
self.assertTrue(partner_carine.signup_url in sent_email.get('body'),
'body of invitation email does not contain signup url')
'invite: body of invitation email does not contain signup url')

View File

@ -9,6 +9,7 @@
<!-- Avoid auto-including this demo user in any default group -->
<field name="groups_id" eval="[(5,)]"/>
<field name="image">iVBORw0KGgoAAAANSUhEUgAAAJYAAACWCAAAAAAZai4+AAAMQElEQVR4nO2ce4wV1R3Hv7/fmbvrPliQZWEXWFgWEFgQBRNI0dZW7euPxliTRhNbW5NatammoaaPNKmpKRYiUunDFzEU28YYkRaiaX0hYhUELApaKG90l2VZdmVl2WXvnPPrH/c1M3d2uWdmePzB75+9d+7Mmc/8zjm/+T3OWRJciMLnGyBcLmLZyEUsG7mIZSNOAm2IQAAQQAAEAhBRrCYprjkVI6GPpkXFIIuHJYYJ6D/Wsb+97fiJk339A0ZVDa8bP3lG4xhAjDofWGIUcOifW3ceOFr026Q5V980CSbq2I2B5To49dILL/YAQE4tkhlf0ABG3Pzta+FG7EqJKEZL1/IZANgJuzMrBpxvvCuio7QeFcsVWTsLUEMogxSh7IEBcc8dVlrabyeoM40cRfjS3ihc0bC0bJoOp5R5lkLT1ghckbC0WV1bqiF20PCB/fiKpq30VUiVRgUozOgUY3mDaHaFKkmXeq5O/XehsTVDEc2dzX3Szqp1XPJTZCRRD4KYw+yFyBJtaVSTxGIxRkKmp6ZNG9nYNZUQEgAyo66ZnQrTizLPw250JYfF+OmHG7ZsukWKuQzeH1B2XJEMhHt10fM4+HHGDPwQRf1IqD1oZ7uS0hbpkfcYDWj51VgT1Jeo4+12vZhYJ8qoOlaAkrqWsGdtt2stMW3lfC6RyWG/n7ZrLvnIh04mcJ+ksYzqWE8hNmqYXTMRAzJWAXvOxABgDN/XporeNEL1sLLzEbF6deDWGr0AtOKFzxa//8g01Nq1HxFrfmUq0FO6UYmb6r37GcctOptkyuhzoS1+NGibBMSU2nTfu1xMBZYpZdoqZoyGRcVevGF1YvGy/lQ65HQj11m+EyMHZH4xrpi/zRpkYhPVHbH0T5PBckVeuQYYJDpTuM82ykgEKy2d38Wg4RnzsI+MZZCRBJaWVy4DDzqkU1hsHZIlgOXKCmeIQMjBl11jG/nEx3JllaJB32GUwhWt1lTxsbS8VD44FQM3tEbIjsTFMuZkS7E7moNycMnP+qPkbOJiubJ8MJNMCvjKZrGOqBPAMiZ9HYUqixygeZVIOgpVeDq2dBFufR9hIaDSbt1d99RDR7tBXCx0HA+LTJUu+8G9U+CWlGxKHivcSSfS1y5aAM2RWz8rVQymn7+6QJsYifkkqhhBYf3gL030nDxwVrSlzNd/4cZs+CxgGbqbB7f7pUn8TuQAAUnVVbGfNjaWMUX2IV0et9H4WJWTHT8X6ZpYoz3TSNzCnT5VbAaq4hUTkQDW2ZH4Qz77XCIioEwKILayktJWvqKpQ3PN1pIMllY4/f7h42r8lfUwiZjCeP5WRlxp/3VLJQA0LjwWrYAYkCSwXHm1GQA7DgPTdyfBlUhAtiYFJzvYHUw7oiM5pAljabO7xjOhU7gtUr3VL/HHp9BDPZ6clque26zsKikhEhtL+MA6b7JUaGBN3DYTwDK0q0vEd2BT/D5IwMik/VUekb2nbJNsRZIAVlFWpNeyNhAiCah7Qk1ANzWXxG00PhaZGXN9LjLTnIqQ4p2dJGAg+E4fBcnXwsoYdhL/VS3QX329YLhS6VnvVMV3bWIbZDFycAJSTABIpVD/XgIvxWRe1R/NBaAUA5gfYY1IsSTk2PQsagaAinlP9SXi2CTjBhpG95ZuU9Ewj5JxAxNzmjOOjZELyWkGIEZAcWP8vFygAdkFukr3IpaNXMSykeg5CI/bEMgk5X/QkVeCRzMQIia8duE/iwDRFMXAWmPliU6o6uwhs9eT6qZ09dgsVNdT82aOBgAtbKk1m04UgYCZobt27HhLLctm14w8s2RYbkkGG17dIAyAzMhh1zW3tMyb05gCRJO3e0u4V0lidDoTK3e/ufLez9cqVG7PuwpGfwsOcsH+s/mg2pUHAaBq7veffKNLRETSbol5gBKwjE6ntYhI/4F/PXzjpDIAoIo3CyG9lr4FcEBE5GCRJ9R35TsoZwBINd+8fENbBi1dQo5iaCxjXNcVETndvfH3d3wus1BGOaqMVspA4TQthyeDATDu9FbqjHtyPhSxykyP8Tfc/+yeUyIi2nWH9soGxzI6nbnDwO7V919bT1kiJoLCbyTtPdeVrWNAYHzTrwotB5vAAIiVwwBQOev2Fe/1ioiYoXo0FMtoN3PJwKH1S2+9fExmMDu5ma5wh0n7r0jLYjhEFfsCvqkr/x6WNxCUXV0/fPpNi9d/rEVE9CBsRTNRRDKrP/p2bv5o5+4OAHBgRPJlAaVv/ENw1SuZk4DA9AXsjXIXPHkrZa2QaIDA9NmJXWtQP+PK2dc0OQxooMi0ebEERkgRYPbv2rHzncMaABPE+BYZsZ73VEVwQRLTAQiQPjozwOW4t+x5wJOSkCyFtLevR/nsBbNmT68BoIXYG8XlsTSYoKAPtW3btr31GAAwS3HlBISb69ygkknvgoDNkaKkCJvvLT4VMNoGADHM6S1bUFl/+fx5k8aVAzCeCqSnQ/v3vHD/9fWZxkL3fWR+QsuJ4JIZIx0jQFBYKoFBJ2n50aA2mzhbNK79wsJ/7Pdemcd6+6HbWioZAKngur+AKPwlmIbU8hYDcPCTIJY2H1YPadyJM/Glqp57558O5NaXZLCMkesBwEmVUMhlXNUXmD6urAABDm4JArty16CrNzxsynEA4Jnc5Rl/iwyanXKGm9ZnfnUbtW1dYHOBoBUMCHrAvgYM/++vJSRKRLsuOFU5MzfsOdfuBNctPc/yWGAmEvaDAMGJgYC26dHPuEQnRdIVjQEswujSE4uaNmz0reIUdg9CAEHHp75mDO9YWXJWiTBlZO5zHmt86OKPcFHmCb+jRt1tGZyj/pXDgiWnSlUWCNNY/NoCGpzSk1Ka1273rhURnGiDAMI9x73aMvzBmtJ3OxCmIu+25Q6Nqyn1ckCod6nvOw71cmZD52H/eb/rLdn9JYOW/Jc8Vk1d6VgwtHa3Tw37cqr+xKMtrXY8V7y+efBGKxrznZf7a6jWIrMo3LPSd/a+3IdDvvMW9ZbcJAijJuYR8lhlE2wSnoZWdhbGMmFPFheefZ1GbX3eKrnbWCdBLNBEGyxR7U/nx6ewuy/Td4SufCsCLHNLnoYAoblgpAtRdVPpUAA0Huss7JHp+iyDJWjPJwPtpiEAzCh8zDVCaLQq1Ig6uDrXQ4JPjufU8ml3buqR/LbPQlkQzCn0VwGrPlgjOUMr9ETOjTI42MPZpk92ZZ/O8NvP22xoI6meWowFNIwo2p8zlBj+z4v5PtqdP9zTmf/4SNoqZJfLxoRpa/gltrWHx3N1Q9mVGwA0kH0pat60tvQ9jAAY44aJ51u2PakaaYel6fUXM6OLB/ZksYTRkZuTj7hWSSrCJOjiTjSYYNMKAMbjGXWw25bXFg6DAGje+He7rZLiefX40m7jLLE0vbyNDWDQ6jGinRmdm0VpuyGhaaoHxoM10RILnP4jAAj29ecXAOEoGNDqjdfsbBbBOxG9WPW2WIbX7WUD4KDHtB/JeBKPpG3r1RO99/dgNdpWJ4W7l8NAsN/TzLEBiOENL1uuhWBMTZkQbRHGVNvWcjX9+bAjjD0ebbX3ADLwYNi+mqGEMM2rXg9WlY1rk4GgnhXQ3F+IpQV9ndDq9ddsdywLZoRiATWWG6oAgFZ1KnS1FuIAlm6QWWLdjuZxXpaCtkzFKNvGYPjQGsLRo4W3DA+0Qb1su18ZhNomr1IKhELjI1S+aWk3DnntuXyC9MMh+8jOJOObJBwLY+3XoGjevUYO+B7nY7xgabMAMGb6pm4BizAqwtIYweO0y3fkY3dZlNLFXN837+u00SKCzYmhLRtafWk1943NVq5D9rLJ/hHkSQZtt4hg86Loi/63xuXFW8HPKISyHb6sawHLSJela5OcMCZ3+DbeeB+sujESVvCaKJUnNF7qO+Ad8qmmSFjBeRKh5kZodnz/GsGjLYNo2kpADFr8T+M1hLDcZZycCKb4NeLDsvVPkxKS8mmDYgHjYBWTJSgN4/0kni+EusrYy9QiCaGp3G+BfVijLVJvSQqhJeWfvz6s2gRWz0URwhWBI14sqR5zTmnyImWBEe8baOfLcJEe0zwEFjD2XNLkhTC8PgDi/2Yb7ycjhCnBf+PgxxoXe410NGkJ3teLRZhwnrCmBw/4tXVpVezVyBHE8LTgIb+2Kkach6lIMnps0KH1YZnhDecBizFhZHDs/B9R17D2kvkawAAAAABJRU5ErkJggg==</field>
<field name="share" eval="True"/>
</record>
<record id="portal.group_anonymous" model="res.groups">

View File

@ -109,7 +109,8 @@
<group name="inventory">
<group name="status" string="Status">
<field name="state"/>
<field name="product_manager"/>
<field name="product_manager"
context="{'default_groups_ref': ['base.group_user', 'base.group_sale_manager']}"/>
</group>
<group name="Weights" groups="product.group_stock_packaging" string="Weights">
<field digits="(14, 3)" name="volume" attrs="{'readonly':[('type','=','service')]}"/>

View File

@ -100,7 +100,9 @@
<group>
<group>
<field name="privacy_visibility"/>
<field name="user_id" string="Project Manager" attrs="{'readonly':[('state','in',['close', 'cancelled'])]}"/>
<field name="user_id" string="Project Manager"
attrs="{'readonly':[('state','in',['close', 'cancelled'])]}"
context="{'default_groups_ref': ['base.group_user', 'project.group_project_manager']}"/>
</group>
<group>
<field name="partner_id" on_change="onchange_partner_id(partner_id)"/>
@ -381,7 +383,10 @@
<group>
<group>
<field name="project_id" on_change="onchange_project(project_id)" context="{'default_use_tasks':1}"/>
<field name="user_id" attrs="{'readonly':[('state','in',['done', 'cancelled'])]}" options='{"no_open": True}'/>
<field name="user_id"
attrs="{'readonly':[('state','in',['done', 'cancelled'])]}"
options='{"no_open": True}'
context="{'default_groups_ref': ['base.group_user', 'project.group_project_user']}"/>
<field name="planned_hours" widget="float_time"
groups="project.group_time_work_estimation_tasks"
on_change="onchange_planned(planned_hours, effective_hours)"/>

View File

@ -69,7 +69,8 @@
<field name="categ_ids" widget="many2many_tags"/>
<group>
<group groups="base.group_user">
<field name="user_id"/>
<field name="user_id"
context="{'default_groups_ref': ['base.group_user', 'project.group_project_user']}"/>
<field name="partner_id" on_change="onchange_partner_id(partner_id, email_from)"/>
<field name="email_from"/>
<label for="project_id" groups="base.group_user"/>

View File

@ -729,7 +729,7 @@ class sale_order_line(osv.osv):
'salesman_id':fields.related('order_id', 'user_id', type='many2one', relation='res.users', store=True, string='Salesperson'),
'company_id': fields.related('order_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
}
_order = 'order_id desc, sequence'
_order = 'order_id desc, sequence, id'
_defaults = {
'product_uom' : _get_uom_id,
'discount': 0.0,

View File

@ -88,7 +88,7 @@ openerp.web_linkedin = function(instance) {
$("body").append(self.$linkedin);
var tag = document.createElement('script');
tag.type = 'text/javascript';
tag.src = "http://platform.linkedin.com/in.js";
tag.src = "https://platform.linkedin.com/in.js";
tag.innerHTML = 'api_key : ' + self.api_key + '\nauthorize : true\nscope: r_network r_basicprofile'; // r_contactinfo r_fullprofile r_emailaddress';
document.getElementsByTagName('head')[0].appendChild(tag);