[MERGE] upstream

bzr revid: fme@openerp.com-20140312152942-egbzui0drs85wt7i
This commit is contained in:
Fabien Meghazi 2014-03-12 16:29:42 +01:00
commit a8f513f4f2
101 changed files with 961 additions and 596 deletions

View File

@ -30,7 +30,7 @@ from openerp import SUPERUSER_ID
from openerp import tools
from openerp.osv import fields, osv, expression
from openerp.tools.translate import _
from openerp.tools.float_utils import float_round
from openerp.tools.float_utils import float_round as round
import openerp.addons.decimal_precision as dp
@ -2144,7 +2144,7 @@ class account_tax(osv.osv):
tax_compute_precision = precision
if taxes and taxes[0].company_id.tax_calculation_rounding_method == 'round_globally':
tax_compute_precision += 5
totalin = totalex = float_round(price_unit * quantity, precision)
totalin = totalex = round(price_unit * quantity, precision)
tin = []
tex = []
for tax in taxes:

View File

@ -168,7 +168,7 @@
on_change="onchange_partner_id(type,partner_id,date_invoice,payment_term, partner_bank_id,company_id)"
context="{'default_customer': 0, 'search_default_supplier': 1, 'default_supplier': 1}"
domain="[('supplier', '=', True)]"/>
<field name="fiscal_position" widget="selection"/>
<field name="fiscal_position" options="{'no_create': True}"/>
<field name="origin"/>
<field name="supplier_invoice_number"/>
<label for="reference_type"/>
@ -183,7 +183,7 @@
<field domain="[('company_id', '=', company_id), ('type', '=', 'payable')]"
name="account_id" groups="account.group_account_user"/>
<field name="journal_id" groups="account.group_account_user"
on_change="onchange_journal_id(journal_id, context)" widget="selection"/>
on_change="onchange_journal_id(journal_id, context)" options="{'no_create': True}"/>
<field name="currency_id" groups="base.group_multi_currency"/>
<field name="check_total" groups="account.group_supplier_inv_check_total"/>
</group>
@ -253,7 +253,7 @@
<field domain="[('partner_id', '=', partner_id)]" name="partner_bank_id" on_change="onchange_partner_bank(partner_bank_id)"/>
<field name="user_id" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'account.group_account_invoice']}"/>
<field name="name" invisible="1"/>
<field name="payment_term" widget="selection"/>
<field name="payment_term" options="{'no_create': True}"/>
</group>
<group>
<field name="move_id" groups="account.group_account_user"/>
@ -324,12 +324,12 @@
context="{'search_default_customer':1, 'show_address': 1}"
options='{"always_reload": True}'
domain="[('customer', '=', True)]"/>
<field name="fiscal_position" widget="selection" />
<field name="fiscal_position" options="{'no_create': True}" />
</group>
<group>
<field name="date_invoice"/>
<field name="journal_id" groups="account.group_account_user"
on_change="onchange_journal_id(journal_id, context)" widget="selection"/>
on_change="onchange_journal_id(journal_id, context)" options="{'no_create': True}"/>
<field domain="[('company_id', '=', company_id),('type','=', 'receivable')]"
name="account_id" groups="account.group_account_user"/>

View File

@ -1601,7 +1601,6 @@
<label for="value_amount" string="Amount To Pay" attrs="{'invisible':[('value','=','balance')]}"/>
<div attrs="{'invisible':[('value','=','balance')]}">
<field name="value_amount" class="oe_inline"/>
<label string="%%" class="oe_inline" attrs="{'invisible':['!',('value','=','procent')]}" />
</div>
</group>
<group string="Due Date Computation">

View File

@ -41,6 +41,23 @@ class account_voucher(osv.osv):
'number': fields.char('Number', size=32),
}
def _amount_to_text(self, cr, uid, amount, currency_id, context=None):
# Currency complete name is not available in res.currency model
# Exceptions done here (EUR, USD, BRL) cover 75% of cases
# For other currencies, display the currency code
currency = self.pool['res.currency'].browse(cr, uid, currency_id, context=context)
if currency.name.upper() == 'EUR':
currency_name = 'Euro'
elif currency.name.upper() == 'USD':
currency_name = 'Dollars'
elif currency.name.upper() == 'BRL':
currency_name = 'reais'
else:
currency_name = currency.name
#TODO : generic amount_to_text is not ready yet, otherwise language (and country) and currency can be passed
#amount_in_word = amount_to_text(amount, context=context)
return amount_to_text(amount, currency=currency_name)
def onchange_amount(self, cr, uid, ids, amount, rate, partner_id, journal_id, currency_id, ttype, date, payment_rate_currency_id, company_id, context=None):
""" Inherited - add amount_in_word and allow_check_writting in returned value dictionary """
if not context:
@ -48,22 +65,7 @@ class account_voucher(osv.osv):
default = super(account_voucher, self).onchange_amount(cr, uid, ids, amount, rate, partner_id, journal_id, currency_id, ttype, date, payment_rate_currency_id, company_id, context=context)
if 'value' in default:
amount = 'amount' in default['value'] and default['value']['amount'] or amount
# Currency complete name is not available in res.currency model
# Exceptions done here (EUR, USD, BRL) cover 75% of cases
# For other currencies, display the currency code
currency = self.pool['res.currency'].browse(cr, uid, currency_id, context=context)
if currency.name.upper() == 'EUR':
currency_name = 'Euro'
elif currency.name.upper() == 'USD':
currency_name = 'Dollars'
elif currency.name.upper() == 'BRL':
currency_name = 'reais'
else:
currency_name = currency.name
#TODO : generic amount_to_text is not ready yet, otherwise language (and country) and currency can be passed
#amount_in_word = amount_to_text(amount, context=context)
amount_in_word = amount_to_text(amount, currency=currency_name)
amount_in_word = self._amount_to_text(cr, uid, amount, currency_id, context=context)
default['value'].update({'amount_in_word':amount_in_word})
if journal_id:
allow_check_writing = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context).allow_check_writing
@ -92,6 +94,19 @@ class account_voucher(osv.osv):
},
'nodestroy': True
}
def create(self, cr, uid, vals, context=None):
if vals.get('amount') and vals.get('journal_id') and 'amount_in_word' not in vals:
vals['amount_in_word'] = self._amount_to_text(cr, uid, vals['amount'], vals.get('currency_id') or \
self.pool['account.journal'].browse(cr, uid, vals['journal_id'], context=context).currency.id or \
self.pool['res.company'].browse(cr, uid, vals['company_id']).currency_id.id, context=context)
return super(account_voucher, self).create(cr, uid, vals, context=context)
def write(self, cr, uid, ids, vals, context=None):
if vals.get('amount') and vals.get('journal_id') and 'amount_in_word' not in vals:
vals['amount_in_word'] = self._amount_to_text(cr, uid, vals['amount'], vals.get('currency_id') or \
self.pool['account.journal'].browse(cr, uid, vals['journal_id'], context=context).currency.id or \
self.pool['res.company'].browse(cr, uid, vals['company_id']).currency_id.id, context=context)
return super(account_voucher, self).write(cr, uid, ids, vals, context=context)
def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
"""

View File

@ -149,7 +149,7 @@
<para style="terp_default_8">
<font color="white"> </font>
</para>
<pre style="terp_default_9_followup_id">[[ format(get_text(o,data['form']['followup_id'])) ]]</pre>
<para style="terp_default_9"><pre style="terp_default_9_followup_id">[[ format(get_text(o,data['form']['followup_id'])) ]]</pre></para>
<para style="terp_default_9">
<font color="white"> </font>
</para>

View File

@ -463,14 +463,12 @@ def process_data(cr, uid, pool, res_ids, model, method, old_values=None, new_val
# if at least one modification has been found
for model_id, resource_id in lines:
line_model = pool.get('ir.model').browse(cr, SUPERUSER_ID, model_id).model
name = pool.get(line_model).name_get(cr, uid, [resource_id])[0][1]
vals = {
'method': method,
'object_id': model_id,
'user_id': uid,
'res_id': resource_id,
'name': name,
}
if (model_id, resource_id) not in old_values and method not in ('copy', 'read'):
# the resource was not existing so we are forcing the method to 'create'
@ -481,7 +479,11 @@ def process_data(cr, uid, pool, res_ids, model, method, old_values=None, new_val
# the resource is not existing anymore so we are forcing the method to 'unlink'
# (because it could also come with the value 'write' if we are deleting the
# record through a one2many field)
name = old_values[(model_id, resource_id)]['value'].get('name',False)
vals.update({'method': 'unlink'})
else :
name = pool[line_model].name_get(cr, uid, [resource_id])[0][1]
vals.update({'name': name})
# create the audittrail log in super admin mode, only if a change has been detected
if lines[(model_id, resource_id)]:
log_id = pool.get('audittrail.log').create(cr, SUPERUSER_ID, vals)

View File

@ -15,6 +15,7 @@ from openerp.tools.translate import _
_logger = logging.getLogger(__name__)
#----------------------------------------------------------
# helpers
#----------------------------------------------------------
@ -30,11 +31,15 @@ def fragment_to_query_string(func):
var s = l.search ? (l.search === '?' ? '' : '&') : '?';
r = l.pathname + l.search + s + q;
}
if (r == l.pathname) {
r = '/';
}
window.location = r;
</script></head><body></body></html>"""
return func(self, *a, **kw)
return wrapper
#----------------------------------------------------------
# Controller
#----------------------------------------------------------
@ -91,6 +96,7 @@ class OAuthLogin(openerp.addons.web.controllers.main.Home):
return response
class OAuthController(http.Controller):
@http.route('/auth_oauth/signin', type='http', auth='none')

View File

@ -19,31 +19,31 @@
<t t-foreach="js" t-as="js_file">
<script type="text/javascript" t-att-src="js_file"></script>
</t>
<script type="text/javascript">
$(function() {
var s = new openerp.init(<t t-raw="modules"/>);
var login_form = new openerp.web.LoginForm($('.oe_signup_form'));
});
</script>
</t>
<script type="text/javascript">
$(function() {
var s = new openerp.init(<t t-raw="modules"/>);
var login_form = new openerp.web.LoginForm($('.oe_signup_form'));
});
</script>
<t t-set="reset_without_token" t-value="mode == 'reset' and not token"/>
<form class="oe_signup_form" role="form" method="post">
<t t-call="web.database_select"/>
<div class="form-group field-login">
<label for="login" class="control-label">Your Email</label>
<input type="email" name="login" t-att-value="login" id="login" class="form-control" autofocus="autofocus"
required="required" t-att-disabled="'disabled' if mode == 'reset' and token else None"/>
<input type="hidden" name="login" t-att-value="login" t-if="mode == 'reset' and token"/>
</div>
<div class="form-group field-name" t-if="not reset_without_token">
<label for="name" class="control-label">Your Name</label>
<input type="text" name="name" t-att-value="name" id="name" class="form-control" placeholder="e.g. John Doe"
required="required" autofocus="autofocus" t-att-disabled="'disabled' if mode == 'reset' and token else None"/>
</div>
<div class="form-group field-login">
<label for="login" class="control-label">Your Email</label>
<input type="email" name="login" t-att-value="login" id="login" class="form-control"
t-att-autofocus="'autofocus' if reset_without_token else None"
required="required" t-att-disabled="'disabled' if mode == 'reset' and token else None"/>
<input type="hidden" name="login" t-att-value="login" t-if="mode == 'reset' and token"/>
required="required" t-att-disabled="'disabled' if mode == 'reset' and token else None"
t-att-autofocus="'autofocus' if mode != 'reset' and login else None"/>
</div>
<div class="form-group field-password" t-if="not reset_without_token">

View File

@ -114,6 +114,8 @@ class ir_import(orm.TransientModel):
elif field['type'] == 'one2many' and depth:
f['fields'] = self.get_fields(
cr, uid, field['relation'], context=context, depth=depth-1)
if self.pool['res.users'].has_group(cr, uid, 'base.group_no_one'):
f['fields'].append({'id' : '.id', 'name': '.id', 'string': _("Database ID"), 'required': False, 'fields': []})
fields.append(f)

View File

@ -223,7 +223,7 @@ class calendar_attendee(osv.Model):
})
for attendee in self.browse(cr, uid, ids, context=context):
if attendee.email and email_from:
if attendee.email and email_from and attendee.email != email_from:
ics_file = self.get_ics_file(cr, uid, attendee.event_id, context=context)
mail_id = template_pool.send_mail(cr, uid, template_id, attendee.id, context=local_context)
@ -363,7 +363,7 @@ class calendar_alarm_manager(osv.AbstractModel):
"""
filter_user = """
LEFT JOIN calendar_event_res_partner_rel AS part_rel ON part_rel.calendar_event_id = cal.id
RIGHT JOIN calendar_event_res_partner_rel AS part_rel ON part_rel.calendar_event_id = cal.id
AND part_rel.res_partner_id = %s
"""
@ -1515,7 +1515,7 @@ class calendar_event(osv.Model):
continue
if r['class'] == 'private':
for f in r.keys():
if f not in ('id', 'date', 'date_deadline', 'duration', 'user_id', 'state', 'interval', 'count'):
if f not in ('id', 'date', 'date_deadline', 'duration', 'user_id', 'state', 'interval', 'count', 'recurrent_id_date'):
if isinstance(r[f], list):
r[f] = []
else:

View File

@ -136,7 +136,7 @@
<strong>${object.event_id.name}</strong>
</div>
<div style="height: 50px;text-align: left;font-size : 14px;border-collapse: separate;margin-top:10px">
<strong style="margin-left:12px">Hello ${object.cn}</strong> ,<br/><p style="margin-left:12px">${object.event_id.user_id.partner_id.name} invited you for the ${object.event_id.name} meeting of ${object.event_id.user_id.company_id.name}.</p>
<strong style="margin-left:12px">Dear ${object.cn}</strong> ,<br/><p style="margin-left:12px">${object.event_id.user_id.partner_id.name} invited you for the ${object.event_id.name} meeting of ${object.event_id.user_id.company_id.name}.</p>
</div>
<div style="height: auto;margin-left:12px;margin-top:30px;">
<table>
@ -151,50 +151,58 @@
</td>
<td>
<table cellspacing="0" cellpadding="0" border="0" style="margin-top: 15px; margin-left: 10px;font-size: 16px;">
% if object.event_id.location:
<tr style=" height: 30px;">
<tr>
<td style="vertical-align:top;">
<div style="height: 25px; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
Where
</div>
</td>
<td colspan="1" style="vertical-align:top;">
<div style = "font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 14px" >
: ${object.event_id.location}
<span style= "color:#A9A9A9; ">(<a href="http://maps.google.com/maps?oi=map&q=${object.event_id.location}">View Map</a>)
</span>
</div>
% if object.event_id.location:
<div style="width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
Where
</div>
% endif
</td>
<td style="vertical-align:top;">
% if object.event_id.location:
<div style = "font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 14px" >
: ${object.event_id.location}
<span style= "color:#A9A9A9; ">(<a href="http://maps.google.com/maps?oi=map&q=${object.event_id.location}">View Map</a>)
</span>
</div>
% endif
</td>
</tr>
% endif
% if object.event_id.description :
<tr style=" height:auto;">
<tr>
<td style="vertical-align:top;">
<div style="height:auto; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
What
</div>
% if object.event_id.description :
<div style="width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
What
</div>
% endif
</td>
<td colspan="3" style="vertical-align:text-top;">
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
: ${object.event_id.description}
</div>
<td style="vertical-align:text-top;">
% if object.event_id.description :
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
: ${object.event_id.description}
</div>
% endif
</td>
</tr>
% endif
% if not object.event_id.allday and object.event_id.duration:
<tr style=" height:auto;">
<tr>
<td style="vertical-align:top;">
<div style="height:auto; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
Duration
</div>
% if not object.event_id.allday and object.event_id.duration:
<div style="height:auto; width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
Duration
</div>
% endif
</td>
<td colspan="3" style="vertical-align:text-top;">
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
: ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))}
</div>
% if not object.event_id.allday and object.event_id.duration:
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
: ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))}
</div>
% endif
</td>
</tr>
% endif
</tr>
<tr style=" height: 30px;">
<td style="height: 25px;width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
<div>
@ -219,9 +227,9 @@
</table>
</div>
<div style="height: auto;width:450px; margin:0 auto;padding-top:20px;padding-bottom:40px;">
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#8A89BA;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;" href="${ctx['base_url']}/calendar/meeting/accept?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">Accept</a>
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#808080;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;" href="${ctx['base_url']}/calendar/meeting/decline?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">Decline</a>
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#D8D8D8;text-decoration: none;color:#FFFFFF;" href="${ctx['base_url']}/calendar/meeting/view?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">View</a>
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#8A89BA;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;" href="/calendar/meeting/accept?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">Accept</a>
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#808080;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;" href="/calendar/meeting/decline?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">Decline</a>
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#D8D8D8;text-decoration: none;color:#FFFFFF;" href="/calendar/meeting/view?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">View</a>
</div>
</div>
</body>
@ -259,13 +267,13 @@
<strong>${object.event_id.name}</strong>
</div>
<div style="height: 50px;text-align: left;font-size : 14px;border-collapse: separate;margin-top:10px">
<strong style="margin-left:12px">Hello ${object.cn}</strong> ,<br/>
<strong style="margin-left:12px">Dear ${object.cn}</strong> ,<br/>
<p style="margin-left:12px">The date of the meeting has been changed...<br/>
The meeting created by ${object.event_id.user_id.partner_id.name} is now scheduled for : ${object.event_id.display_time}.</p>
</div>
<div style="height: auto;margin-left:12px;margin-top:30px;">
<table>
<tr>
<tr>
<td>
<div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;border-color:#ffffff;background:#8a89ba;padding-top: 4px;">${object.event_id.get_interval(object.event_id.date, 'dayname')}</div>
<div style="font-size:48px;min-height:auto;font-weight:bold;text-align:center;color: #5F5F5F;background-color: #E1E2F8;width: 130px;">
@ -276,50 +284,58 @@
</td>
<td>
<table cellspacing="0" cellpadding="0" border="0" style="margin-top: 15px; margin-left: 10px;font-size: 16px;">
% if object.event_id.location:
<tr style=" height: 30px;">
<tr>
<td style="vertical-align:top;">
<div style="height: 25px; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
Where
</div>
</td>
<td colspan="1" style="vertical-align:top;">
<div style = "font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 14px" >
: ${object.event_id.location}
<span style= "color:#A9A9A9; ">(<a href="http://maps.google.com/maps?oi=map&q=${object.event_id.location}">View Map</a>)
</span>
</div>
% if object.event_id.location:
<div style="width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
Where
</div>
% endif
</td>
<td style="vertical-align:top;">
% if object.event_id.location:
<div style = "font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 14px" >
: ${object.event_id.location}
<span style= "color:#A9A9A9; ">(<a href="http://maps.google.com/maps?oi=map&q=${object.event_id.location}">View Map</a>)
</span>
</div>
% endif
</td>
</tr>
% endif
% if object.event_id.description :
<tr style=" height:auto;">
<tr>
<td style="vertical-align:top;">
<div style="height:auto; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
What
</div>
% if object.event_id.description :
<div style="width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
What
</div>
% endif
</td>
<td colspan="3" style="vertical-align:text-top;">
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
: ${object.event_id.description}
</div>
<td style="vertical-align:text-top;">
% if object.event_id.description :
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
: ${object.event_id.description}
</div>
% endif
</td>
</tr>
% endif
% if not object.event_id.allday and object.event_id.duration:
<tr style=" height:auto;">
<tr>
<td style="vertical-align:top;">
<div style="height:auto; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
Duration
</div>
% if not object.event_id.allday and object.event_id.duration:
<div style="height:auto; width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
Duration
</div>
% endif
</td>
<td colspan="3" style="vertical-align:text-top;">
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
: ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))}
</div>
% if not object.event_id.allday and object.event_id.duration:
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
: ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))}
</div>
% endif
</td>
</tr>
% endif
</tr>
<tr style=" height: 30px;">
<td style="height: 25px;width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
<div>
@ -341,12 +357,12 @@
</table>
</td>
</tr>
</table>
</table>
</div>
<div style="height: auto;width:450px; margin:0 auto;padding-top:20px;padding-bottom:40px;">
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#8A89BA;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;" href="${ctx['base_url']}/calendar/meeting/accept?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">Accept</a>
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#808080;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;" href="${ctx['base_url']}/calendar/meeting/decline?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">Decline</a>
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#D8D8D8;text-decoration: none;color:#FFFFFF;" href="${ctx['base_url']}/calendar/meeting/view?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">View</a>
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#8A89BA;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;" href="/calendar/meeting/accept?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">Accept</a>
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#808080;margin : 0 15px 0 0;text-decoration: none;color:#FFFFFF;" href="/calendar/meeting/decline?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">Decline</a>
<a style="padding: 8px 30px 8px 30px;border-radius: 6px;border: 1px solid #CCCCCC;background:#D8D8D8;text-decoration: none;color:#FFFFFF;" href="/calendar/meeting/view?db=${ctx['dbname']}&token=${object.access_token}&action=${ctx['action_id']}&id=${object.event_id.id}">View</a>
</div>
</div>
</body>
@ -384,8 +400,8 @@
<strong>${object.event_id.name}</strong>
</div>
<div style="height: 50px;text-align: left;font-size : 14px;border-collapse: separate;margin-top:10px">
<strong style="margin-left:12px">Hello ${object.cn}</strong> ,<br/>
<p style="margin-left:12px">this it a rmeinder for the event below : </p>
<strong style="margin-left:12px">Dear ${object.cn}</strong> ,<br/>
<p style="margin-left:12px">this it a reminder for the event below : </p>
</div>
<div style="height: auto;margin-left:12px;margin-top:30px;">
<table>
@ -400,50 +416,58 @@
</td>
<td>
<table cellspacing="0" cellpadding="0" border="0" style="margin-top: 15px; margin-left: 10px;font-size: 16px;">
% if object.event_id.location:
<tr style=" height: 30px;">
<tr>
<td style="vertical-align:top;">
<div style="height: 25px; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
Where
</div>
</td>
<td colspan="1" style="vertical-align:top;">
<div style = "font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 14px" >
: ${object.event_id.location}
<span style= "color:#A9A9A9; ">(<a href="http://maps.google.com/maps?oi=map&q=${object.event_id.location}">View Map</a>)
</span>
</div>
% if object.event_id.location:
<div style="width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
Where
</div>
% endif
</td>
<td style="vertical-align:top;">
% if object.event_id.location:
<div style = "font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif; font-size: 14px" >
: ${object.event_id.location}
<span style= "color:#A9A9A9; ">(<a href="http://maps.google.com/maps?oi=map&q=${object.event_id.location}">View Map</a>)
</span>
</div>
% endif
</td>
</tr>
% endif
% if object.event_id.description :
<tr style=" height:auto;">
<tr>
<td style="vertical-align:top;">
<div style="height:auto; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
What
</div>
% if object.event_id.description :
<div style="width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
What
</div>
% endif
</td>
<td colspan="3" style="vertical-align:text-top;">
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
: ${object.event_id.description}
</div>
<td style="vertical-align:text-top;">
% if object.event_id.description :
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
: ${object.event_id.description}
</div>
% endif
</td>
</tr>
% endif
% if not object.event_id.allday and object.event_id.duration:
<tr style=" height:auto;">
<tr>
<td style="vertical-align:top;">
<div style="height:auto; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
Duration
</div>
% if not object.event_id.allday and object.event_id.duration:
<div style="height:auto; width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
Duration
</div>
% endif
</td>
<td colspan="3" style="vertical-align:text-top;">
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
: ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))}
</div>
% if not object.event_id.allday and object.event_id.duration:
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
: ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))}
</div>
% endif
</td>
</tr>
% endif
</tr>
<tr style=" height: 30px;">
<td style="height: 25px;width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
<div>

View File

@ -313,7 +313,10 @@ class crm_lead(format_address, osv.osv):
stage = self.pool.get('crm.case.stage').browse(cr, uid, stage_id, context=context)
if not stage.on_change:
return {'value': {}}
return {'value': {'probability': stage.probability}}
vals = {'probability': stage.probability}
if stage.probability >= 100 or (stage.probability == 0 and stage.sequence > 1):
vals['date_closed'] = fields.datetime.now()
return {'value': vals}
def on_change_partner_id(self, cr, uid, ids, partner_id, context=None):
values = {}

View File

@ -58,7 +58,7 @@
<field name="view_mode">tree,calendar</field>
<field name="view_id" ref="crm_case_inbound_phone_tree_view"/>
<field name="domain">[]</field>
<field name="context">{'default_state': 'done'}</field>
<field name="context">{'search_default_state': 'done', 'default_state': 'done'}</field>
<field name="search_view_id" ref="crm.view_crm_case_phonecalls_filter"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">

View File

@ -167,6 +167,7 @@
<search string="Search Phonecalls">
<field name="name" string="Phonecalls"/>
<field name="date"/>
<field name="state"/>
<separator/>
<filter icon="terp-gtk-go-back-rtl" string="To Do" name="current" domain="[('state','=','open')]"/>
<separator/>

View File

@ -65,7 +65,7 @@
Salesman create a mass convert wizard and convert all the leads.
-
!python {model: crm.lead2opportunity.partner.mass}: |
context.update({'active_model': 'crm.lead', 'active_ids': [ref("test_crm_lead_01"), ref("test_crm_lead_02"), ref("test_crm_lead_03"), ref("test_crm_lead_04"), ref("test_crm_lead_05"), ref("test_crm_lead_06")], 'active_id': ref("test_crm_lead_01")})
context.update({'active_model': 'crm.lead', 'active_ids': [ref("test_crm_lead_01"), ref("test_crm_lead_02"), ref("test_crm_lead_03"), ref("test_crm_lead_04"), ref("test_crm_lead_05"), ref("test_crm_lead_06")], 'active_id': ref("test_crm_lead_01"), 'no_force_assignation': False})
id = self.create(cr, uid, {'user_ids': [(6, 0, [ref('test_res_user_01'), ref('test_res_user_02'), ref('test_res_user_03'), ref('test_res_user_04')])], 'section_id': ref('crm.section_sales_department'), 'deduplicate': False}, context=context)
self.mass_convert(cr, uid, [id], context=context)
-

View File

@ -46,12 +46,12 @@ class crm_lead2opportunity_partner(osv.osv_memory):
results = []
if partner_id:
# Search for opportunities that have the same partner and that arent done or cancelled
ids = lead_obj.search(cr, uid, [('partner_id', '=', partner_id), '|', ('probability', '=', False), ('probability', '<', '100')])
ids = lead_obj.search(cr, uid, [('partner_id', '=', partner_id), '|', ('stage_id.probability', '=', False), ('stage_id.probability', '<', '100')])
for id in ids:
results.append(id)
email = re.findall(r'([^ ,<@]+@[^> ,]+)', email or '')
if email:
ids = lead_obj.search(cr, uid, [('email_from', '=ilike', email[0]), '|', ('probability', '=', False), ('probability', '<', '100')])
ids = lead_obj.search(cr, uid, [('email_from', '=ilike', email[0]), '|', ('stage_id.probability', '=', False), ('stage_id.probability', '<', '100')])
for id in ids:
results.append(id)
return list(set(results))
@ -69,11 +69,10 @@ class crm_lead2opportunity_partner(osv.osv_memory):
if context.get('active_id'):
tomerge = [int(context['active_id'])]
email = False
partner_id = res.get('partner_id')
lead = lead_obj.browse(cr, uid, int(context['active_id']), context=context)
email = lead.partner_id and lead.partner_id.email or lead.email_from
#TOFIX: use mail.mail_message.to_mail
tomerge.extend(self._get_duplicated_leads(cr, uid, partner_id, email))
tomerge = list(set(tomerge))
@ -100,10 +99,8 @@ class crm_lead2opportunity_partner(osv.osv_memory):
else:
user_in_section = False
if not user_in_section:
section_id = False
section_ids = self.pool.get('crm.case.section').search(cr, uid, ['|', ('user_id', '=', user_id), ('member_ids', '=', user_id)], context=context)
if section_ids:
section_id = section_ids[0]
result = self.pool['crm.lead'].on_change_user(cr, uid, ids, user_id, context=context)
section_id = result.get('value') and result['value'].get('section_id') and result['value']['section_id'] or False
return {'value': {'section_id': section_id}}
def view_init(self, cr, uid, fields, context=None):
@ -126,14 +123,17 @@ class crm_lead2opportunity_partner(osv.osv_memory):
lead_ids = vals.get('lead_ids', [])
team_id = vals.get('section_id', False)
data = self.browse(cr, uid, ids, context=context)[0]
for lead_id in lead_ids:
partner_id = self._create_partner(cr, uid, lead_id, data.action, data.partner_id.id, context=context)
# FIXME: cannot pass user_ids as the salesman allocation only works in batch
res = lead.convert_opportunity(cr, uid, [lead_id], partner_id, [], team_id, context=context)
# FIXME: must perform salesman allocation in batch separately here
leads = lead.browse(cr, uid, lead_ids, context=context)
for lead_id in leads:
partner_id = self._create_partner(cr, uid, lead_id.id, data.action, lead_id.partner_id.id, context=context)
res = lead.convert_opportunity(cr, uid, [lead_id.id], partner_id, [], False, context=context)
user_ids = vals.get('user_ids', False)
if context.get('no_force_assignation'):
leads_to_allocate = [lead_id.id for lead_id in leads if not lead_id.user_id]
else:
leads_to_allocate = lead_ids
if user_ids:
lead.allocate_salesman(cr, uid, lead_ids, user_ids, team_id=team_id, context=context)
lead.allocate_salesman(cr, uid, leads_to_allocate, user_ids, team_id=team_id, context=context)
return res
def action_apply(self, cr, uid, ids, context=None):
@ -144,15 +144,19 @@ class crm_lead2opportunity_partner(osv.osv_memory):
if context is None:
context = {}
lead_obj = self.pool['crm.lead']
w = self.browse(cr, uid, ids, context=context)[0]
opp_ids = [o.id for o in w.opportunity_ids]
if w.name == 'merge':
lead_id = self.pool.get('crm.lead').merge_opportunity(cr, uid, opp_ids, w.user_id.id, w.section_id.id, context=context)
lead_id = lead_obj.merge_opportunity(cr, uid, opp_ids, context=context)
lead_ids = [lead_id]
lead = self.pool.get('crm.lead').read(cr, uid, lead_id, ['type'], context=context)
lead = lead_obj.read(cr, uid, lead_id, ['type', 'user_id'], context=context)
if lead['type'] == "lead":
context.update({'active_ids': lead_ids})
self._convert_opportunity(cr, uid, ids, {'lead_ids': lead_ids, 'user_ids': [w.user_id.id], 'section_id': w.section_id.id}, context=context)
elif not context.get('no_force_assignation') or not lead['user_id']:
lead_obj.write(cr, uid, lead_id, {'user_id': w.user_id.id, 'section_id': w.section_id.id}, context=context)
else:
lead_ids = context.get('active_ids', [])
self._convert_opportunity(cr, uid, ids, {'lead_ids': lead_ids, 'user_ids': [w.user_id.id], 'section_id': w.section_id.id}, context=context)
@ -186,11 +190,13 @@ class crm_lead2opportunity_mass_convert(osv.osv_memory):
_columns = {
'user_ids': fields.many2many('res.users', string='Salesmen'),
'section_id': fields.many2one('crm.case.section', 'Sales Team'),
'deduplicate': fields.boolean('Apply deduplication', help='Merge with existing leads/opportunities of each partner'),
'deduplicate': fields.boolean('Apply deduplication', help='Merge with existing leads/opportunities of each partner'),
'action': fields.selection([
('each_exist_or_create', 'Use existing partner or create'),
('nothing', 'Do not link to a customer')
], 'Related Customer', required=True),
# Uncomment me in trunk
# 'force_assignation': fields.boolean('Force assignation', help='If unchecked, this will leave the salesman of duplicated opportunities'),
}
_defaults = {
@ -266,6 +272,10 @@ class crm_lead2opportunity_mass_convert(osv.osv_memory):
active_ids = active_ids.difference(merged_lead_ids)
active_ids = active_ids.union(remaining_lead_ids)
ctx['active_ids'] = list(active_ids)
# Remove me in trunk
ctx['no_force_assignation'] = ctx.get('no_force_assignation', True)
# Uncomment me in trunk
# ctx['no_force_assignation'] = not data.force_assignation
return self.action_apply(cr, uid, ids, context=ctx)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -58,6 +58,8 @@
<group string="Assign opportunities to">
<field name="section_id" groups="base.group_multi_salesteams"/>
<field name="user_ids" widget="many2many_tags"/>
<!-- Uncomment me in trunk -->
<!-- <field name="force_assignation" /> -->
</group>
<label for="opportunity_ids" string="Leads with existing duplicates (for information)" help="Leads that you selected that have duplicates. If the list is empty, it means that no duplicates were found" attrs="{'invisible': [('deduplicate', '=', False)]}"/>
<group attrs="{'invisible': [('deduplicate', '=', False)]}">

View File

@ -9,7 +9,7 @@
<field name="arch" type="xml">
<form string="Merge Leads/Opportunities" version="7.0">
<group string="Assign opportunities to">
<field name="user_id" class="oe_inline" on_change="on_change_user(user_id, context)"/>
<field name="user_id" class="oe_inline" on_change="on_change_user(user_id, section_id, context)"/>
<field name="section_id" class="oe_inline"/>
</group>
<group string="Select Leads/Opportunities">

View File

@ -15,10 +15,10 @@
<div class="oe_title">
<h3>
<span class="oe_grey">( </span>
<field name="partner_latitude" nolabel="1" readonly="1" class="oe_inline"/>
<field name="partner_latitude" nolabel="1" class="oe_inline"/>
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_latitude','&lt;=',0)]}">N </span>
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_latitude','&gt;=',0)]}">S </span>
<field name="partner_longitude" class="oe_inline" readonly="1" nolabel="1"/>
<field name="partner_longitude" class="oe_inline" nolabel="1"/>
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_longitude','&lt;=',0)]}">E </span>
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_longitude','&gt;=',0)]}">W </span>
<span class="oe_grey">) </span>
@ -88,10 +88,10 @@
<div>
<h3>
<span class="oe_grey">( </span>
<field name="partner_latitude" nolabel="1" readonly="1" class="oe_inline"/>
<field name="partner_latitude" nolabel="1" class="oe_inline"/>
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_latitude','&lt;=',0)]}">N </span>
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_latitude','&gt;=',0)]}">S </span>
<field name="partner_longitude" class="oe_inline" readonly="1" nolabel="1"/>
<field name="partner_longitude" class="oe_inline" nolabel="1"/>
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_longitude','&lt;=',0)]}">E </span>
<span class="oe_grey oe_inline" attrs="{'invisible':[('partner_longitude','&gt;=',0)]}">W </span>
<span class="oe_grey">) </span>

View File

@ -24,6 +24,8 @@ import base64
import datetime
import dateutil.relativedelta as relativedelta
import logging
import lxml
import urlparse
import openerp
from openerp import SUPERUSER_ID
@ -61,6 +63,15 @@ try:
'quote': quote,
'urlencode': urlencode,
'datetime': datetime,
'len': len,
'abs': abs,
'min': min,
'max': max,
'sum': sum,
'filter': filter,
'reduce': reduce,
'map': map,
'round': round,
# dateutil.relativedelta is an old-style class and cannot be directly
# instanciated wihtin a jinja2 expression, so a lambda "proxy" is
@ -70,6 +81,7 @@ try:
except ImportError:
_logger.warning("jinja2 not available, templating features will not work!")
class email_template(osv.osv):
"Templates for sending email"
_name = "email.template"
@ -82,7 +94,48 @@ class email_template(osv.osv):
res['model_id'] = self.pool['ir.model'].search(cr, uid, [('model', '=', res.pop('model'))], context=context)[0]
return res
def render_template_batch(self, cr, uid, template, model, res_ids, context=None):
def _replace_local_links(self, cr, uid, html, context=None):
""" Post-processing of html content to replace local links to absolute
links, using web.base.url as base url. """
if not html:
return html
# form a tree
root = lxml.html.fromstring(html)
if not len(root) and root.text is None and root.tail is None:
html = '<div>%s</div>' % html
root = lxml.html.fromstring(html)
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
(base_scheme, base_netloc, bpath, bparams, bquery, bfragment) = urlparse.urlparse(base_url)
def _process_link(url):
new_url = url
(scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
if not scheme and not netloc:
new_url = urlparse.urlunparse((base_scheme, base_netloc, path, params, query, fragment))
return new_url
# check all nodes, replace :
# - img src -> check URL
# - a href -> check URL
for node in root.iter():
if node.tag == 'a':
node.set('href', _process_link(node.get('href')))
elif node.tag == 'img' and not node.get('src', 'data').startswith('data'):
node.set('src', _process_link(node.get('src')))
html = lxml.html.tostring(root, pretty_print=False, method='html')
# this is ugly, but lxml/etree tostring want to put everything in a 'div' that breaks the editor -> remove that
if html.startswith('<div>') and html.endswith('</div>'):
html = html[5:-6]
return html
def render_post_process(self, cr, uid, html, context=None):
html = self._replace_local_links(cr, uid, html, context=context)
return html
def render_template_batch(self, cr, uid, template, model, res_ids, context=None, post_process=False):
"""Render the given template text, replace mako expressions ``${expr}``
with the result of evaluating these expressions with
an evaluation context containing:
@ -125,6 +178,10 @@ class email_template(osv.osv):
if render_result == u"False":
render_result = u""
results[res_id] = render_result
if post_process:
for res_id, result in results.iteritems():
results[res_id] = self.render_post_process(cr, uid, result, context=context)
return results
def get_email_template_batch(self, cr, uid, template_id=False, res_ids=None, context=None):
@ -183,7 +240,7 @@ class email_template(osv.osv):
'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing Mail Server', readonly=False,
help="Optional preferred server for outgoing mails. If not set, the highest "
"priority one will be used."),
'body_html': fields.html('Body', translate=True, help="Rich-text/HTML version of the message (placeholders may be used here)"),
'body_html': fields.html('Body', translate=True, sanitize=False, help="Rich-text/HTML version of the message (placeholders may be used here)"),
'report_name': fields.char('Report Filename', translate=True,
help="Name to use for the generated report file (may contain placeholders)\n"
"The extension can be omitted and will then come from the report type."),
@ -356,17 +413,20 @@ class email_template(osv.osv):
results = dict()
for template, template_res_ids in templates_to_res_ids.iteritems():
# generate fields value for all res_ids linked to the current template
for field in ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to']:
generated_field_values = self.render_template_batch(cr, uid, getattr(template, field), template.model, template_res_ids, context=context)
for field in fields:
generated_field_values = self.render_template_batch(
cr, uid, getattr(template, field), template.model, template_res_ids,
post_process=(field == 'body_html'),
context=context)
for res_id, field_value in generated_field_values.iteritems():
results.setdefault(res_id, dict())[field] = field_value
# update values for all res_ids
for res_id in template_res_ids:
values = results[res_id]
if template.user_signature:
if 'body_html' in fields and template.user_signature:
signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
values['body_html'] = tools.append_content_to_html(values['body_html'], signature)
if values['body_html']:
if values.get('body_html'):
values['body'] = tools.html_sanitize(values['body_html'])
values.update(
mail_server_id=template.mail_server_id.id or False,

View File

@ -162,16 +162,18 @@ class mail_compose_message(osv.TransientModel):
partner_ids += self.pool['res.partner'].exists(cr, SUPERUSER_ID, tpl_partner_ids, context=context)
return partner_ids
def generate_email_for_composer_batch(self, cr, uid, template_id, res_ids, context=None):
def generate_email_for_composer_batch(self, cr, uid, template_id, res_ids, context=None, fields=None):
""" Call email_template.generate_email(), get fields relevant for
mail.compose.message, transform email_cc and email_to into partner_ids """
# filter template values
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'attachments', 'mail_server_id']
if fields is None:
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'mail_server_id']
returned_fields = fields + ['attachments']
values = dict.fromkeys(res_ids, False)
template_values = self.pool.get('email.template').generate_email_batch(cr, uid, template_id, res_ids, context=context)
template_values = self.pool.get('email.template').generate_email_batch(cr, uid, template_id, res_ids, fields=fields, context=context)
for res_id in res_ids:
res_id_values = dict((field, template_values[res_id][field]) for field in fields if template_values[res_id].get(field))
res_id_values = dict((field, template_values[res_id][field]) for field in returned_fields if template_values[res_id].get(field))
res_id_values['body'] = res_id_values.pop('body_html', '')
# transform email_to, email_cc into partner_ids
@ -189,7 +191,10 @@ class mail_compose_message(osv.TransientModel):
""" Override to handle templates. """
# generate template-based values
if wizard.template_id:
template_values = self.generate_email_for_composer_batch(cr, uid, wizard.template_id.id, res_ids, context=context)
template_values = self.generate_email_for_composer_batch(
cr, uid, wizard.template_id.id, res_ids,
fields=['email_to', 'partner_to', 'email_cc', 'attachment_ids', 'mail_server_id'],
context=context)
else:
template_values = dict.fromkeys(res_ids, dict())
# generate composer values
@ -206,8 +211,8 @@ class mail_compose_message(osv.TransientModel):
template_values[res_id].update(composer_values[res_id])
return template_values
def render_template_batch(self, cr, uid, template, model, res_ids, context=None):
return self.pool.get('email.template').render_template_batch(cr, uid, template, model, res_ids, context=context)
def render_template_batch(self, cr, uid, template, model, res_ids, context=None, post_process=False):
return self.pool.get('email.template').render_template_batch(cr, uid, template, model, res_ids, context=context, post_process=post_process)
# Compatibility methods
def generate_email_for_composer(self, cr, uid, template_id, res_id, context=None):

View File

@ -52,7 +52,7 @@ class report_event_registration(osv.osv):
# TOFIX this request won't select events that have no registration
cr.execute(""" CREATE VIEW report_event_registration AS (
SELECT
e.id::char || '/' || coalesce(r.id::char,'') AS id,
e.id::varchar || '/' || coalesce(r.id::varchar,'') AS id,
e.id AS event_id,
e.user_id AS user_id,
r.user_id AS user_id_registration,

View File

@ -35,7 +35,6 @@
<group expand="1" string="Group By...">
<filter string="Participant / Contact" icon="terp-personal" context="{'group_by':'name_registration'}" help="Registration contact"/>
<filter string="Register" icon="terp-personal" context="{'group_by':'user_id_registration'}" help="Registration contact" groups="base.group_no_one"/>
<filter string="Speaker" name="speaker" icon="terp-personal+" context="{'group_by': 'speaker_id'}" groups="base.group_no_one"/>
<filter string="Event Responsible" name="user_id" icon="terp-personal" context="{'group_by': 'user_id'}"/>
<filter string="Event" name="event" icon="terp-crm" context="{'group_by':'event_id', 'max_reg_event_visible':0}"/>
<filter string="Event Type" icon="terp-crm" context="{'group_by':'event_type'}"/>

View File

@ -263,6 +263,10 @@ class hr_expense_expense(osv.osv):
#convert eml into an osv-valid format
lines = map(lambda x:(0,0,self.line_get_convert(cr, uid, x, exp.employee_id.address_home_id, exp.date_confirm, context=context)), eml)
journal_id = move_obj.browse(cr, uid, move_id, context).journal_id
# post the journal entry if 'Skip 'Draft' State for Manual Entries' is checked
if journal_id.entry_posted:
move_obj.button_validate(cr, uid, [move_id], context)
move_obj.write(cr, uid, [move_id], {'line_id': lines}, context=context)
self.write(cr, uid, ids, {'account_move_id': move_id, 'state': 'done'}, context=context)
return True

View File

@ -23,7 +23,7 @@ import time
from datetime import date, datetime, timedelta
from openerp.osv import fields, osv
from openerp.tools import config
from openerp.tools import config, float_compare
from openerp.tools.translate import _
class hr_payslip(osv.osv):
@ -86,6 +86,7 @@ class hr_payslip(osv.osv):
def process_sheet(self, cr, uid, ids, context=None):
move_pool = self.pool.get('account.move')
period_pool = self.pool.get('account.period')
precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Payroll')
timenow = time.strftime('%Y-%m-%d')
for slip in self.browse(cr, uid, ids, context=context):
@ -149,7 +150,7 @@ class hr_payslip(osv.osv):
line_ids.append(credit_line)
credit_sum += credit_line[2]['credit'] - credit_line[2]['debit']
if debit_sum > credit_sum:
if float_compare(credit_sum, debit_sum, precision_digits=precision) == -1:
acc_id = slip.journal_id.default_credit_account_id.id
if not acc_id:
raise osv.except_osv(_('Configuration Error!'),_('The Expense Journal "%s" has not properly configured the Credit Account!')%(slip.journal_id.name))
@ -165,7 +166,7 @@ class hr_payslip(osv.osv):
})
line_ids.append(adjust_credit)
elif debit_sum < credit_sum:
elif float_compare(debit_sum, credit_sum, precision_digits=precision) == -1:
acc_id = slip.journal_id.default_debit_account_id.id
if not acc_id:
raise osv.except_osv(_('Configuration Error!'),_('The Expense Journal "%s" has not properly configured the Debit Account!')%(slip.journal_id.name))

View File

@ -138,7 +138,7 @@ class wizard_multi_charts_accounts(osv.osv_memory):
def _process_taxes_translations(self, cr, uid, obj_multi, company_id, langs, field, context=None):
obj_tax_template = self.pool.get('account.tax.template')
obj_tax = self.pool.get('account.tax')
in_ids = sorted([x.id for x in obj_multi.chart_template_id.tax_template_ids])
in_ids = [x.id for x in obj_multi.chart_template_id.tax_template_ids]
out_ids = obj_tax.search(cr, uid, [('company_id', '=', company_id)], order='id')
return self.process_translations(cr, uid, langs, obj_tax_template, field, in_ids, obj_tax, out_ids, context=context)

View File

@ -186,12 +186,11 @@ class IrAttachment(osv.Model):
def get_attachment_type(self, cr, uid, ids, name, args, context=None):
result = {}
for attachment in self.browse(cr, uid, ids, context=context):
fileext = os.path.splitext(attachment.datas_fname)[1].lower()
if not fileext or not fileext[1:] in self._fileext_to_type:
return 'unknown'
result[attachment.id] = self._fileext_to_type[fileext[1:]]
fileext = os.path.splitext(attachment.datas_fname or '')[1].lower()[1:]
result[attachment.id] = self._fileext_to_type.get(fileext, 'unknown')
return result
_columns = {
'file_type': fields.function(get_attachment_type, type='char', string='File Type'),
'file_type_icon': fields.function(get_attachment_type, type='char', string='File Type Icon'),
'file_type': fields.related('file_type_icon', type='char'), # FIXME remove in trunk
}

View File

@ -161,7 +161,7 @@ class mail_mail(osv.Model):
elif mail.model and mail.res_id:
fragment.update(model=mail.model, res_id=mail.res_id)
url = urljoin(base_url, "/web/login?%s#%s" % (urlencode(query), urlencode(fragment)))
url = urljoin(base_url, "/web?%s#%s" % (urlencode(query), urlencode(fragment)))
return _("""<span class='oe_mail_footer_access'><small>Access your messages and documents <a style='color:inherit' href="%s">in OpenERP</a></small></span>""") % url
else:
return None

View File

@ -351,12 +351,12 @@ class mail_message(osv.Model):
partner_tree = dict((partner[0], partner) for partner in partners)
# 2. Attachments as SUPERUSER, because could receive msg and attachments for doc uid cannot see
attachments = ir_attachment_obj.read(cr, SUPERUSER_ID, list(attachment_ids), ['id', 'datas_fname', 'name', 'file_type'], context=context)
attachments = ir_attachment_obj.read(cr, SUPERUSER_ID, list(attachment_ids), ['id', 'datas_fname', 'name', 'file_type_icon'], context=context)
attachments_tree = dict((attachment['id'], {
'id': attachment['id'],
'filename': attachment['datas_fname'],
'name': attachment['name'],
'file_type': attachment['file_type'],
'file_type_icon': attachment['file_type_icon'],
}) for attachment in attachments)
# 3. Update message dictionaries

View File

@ -88,10 +88,10 @@
-->
<t t-name="mail.thread.message.attachments">
<t t-foreach='widget.attachment_ids' t-as='attachment'>
<t t-if="attachment.file_type !== 'webimage'">
<t t-if="attachment.file_type_icon !== 'webimage'">
<div t-attf-class="oe_attachment #{attachment.upload ? 'oe_uploading' : ''}">
<a t-att-href='attachment.url' target="_blank">
<img t-att-src="'/mail/static/src/img/mimetypes/' + attachment.file_type + '.png'"></img>
<img t-att-src="'/mail/static/src/img/mimetypes/' + attachment.file_type_icon + '.png'"></img>
<div class='oe_name'><t t-raw='attachment.name' /></div>
</a>
<div class='oe_delete oe_e' title="Delete this attachment" t-att-data-id="attachment.id">[</div>
@ -100,7 +100,7 @@
</div>
</div>
</t>
<t t-if="attachment.file_type === 'webimage'">
<t t-if="attachment.file_type_icon === 'webimage'">
<div t-attf-class="oe_attachment oe_preview #{attachment.upload ? 'oe_uploading' : ''}">
<a t-att-href='attachment.url' target="_blank">
<img t-att-src="widget.attachments_resize_image(attachment.id, [100,80])"></img>

View File

@ -352,10 +352,10 @@ class mail_compose_message(osv.TransientModel):
:return dict results: for each res_id, the generated template values for
subject, body, email_from and reply_to
"""
subjects = self.render_template_batch(cr, uid, wizard.subject, wizard.model, res_ids, context)
bodies = self.render_template_batch(cr, uid, wizard.body, wizard.model, res_ids, context)
emails_from = self.render_template_batch(cr, uid, wizard.email_from, wizard.model, res_ids, context)
replies_to = self.render_template_batch(cr, uid, wizard.reply_to, wizard.model, res_ids, context)
subjects = self.render_template_batch(cr, uid, wizard.subject, wizard.model, res_ids, context=context)
bodies = self.render_template_batch(cr, uid, wizard.body, wizard.model, res_ids, context=context, post_process=True)
emails_from = self.render_template_batch(cr, uid, wizard.email_from, wizard.model, res_ids, context=context)
replies_to = self.render_template_batch(cr, uid, wizard.reply_to, wizard.model, res_ids, context=context)
results = dict.fromkeys(res_ids, False)
for res_id in res_ids:
@ -367,7 +367,7 @@ class mail_compose_message(osv.TransientModel):
}
return results
def render_template_batch(self, cr, uid, template, model, res_ids, context=None):
def render_template_batch(self, cr, uid, template, model, res_ids, context=None, post_process=False):
""" Render the given template text, replace mako-like expressions ``${expr}``
with the result of evaluating these expressions with an evaluation context
containing:

View File

@ -6,7 +6,7 @@
<record id="membership_0" model="product.product">
<field name="membership">True</field>
<field eval="time.strftime('%Y-01-01')" name="membership_date_from"/>
<field eval="time.strftime('%Y-12-01')" name="membership_date_to"/>
<field eval="time.strftime('%Y-12-31')" name="membership_date_to"/>
<field name="name">Gold Membership</field>
<field name="list_price">180</field>
<field name="categ_id" ref="product.product_category_1"/>
@ -16,7 +16,7 @@
<record id="membership_1" model="product.product">
<field name="membership">True</field>
<field eval="time.strftime('%Y-01-01')" name="membership_date_from"/>
<field eval="time.strftime('%Y-12-01')" name="membership_date_to"/>
<field eval="time.strftime('%Y-12-31')" name="membership_date_to"/>
<field name="name">Silver Membership</field>
<field name="categ_id" ref="product.product_category_1"/>
<field name="list_price">80</field>
@ -26,7 +26,7 @@
<record id="membership_2" model="product.product">
<field name="membership">True</field>
<field eval="time.strftime('%Y-01-01')" name="membership_date_from"/>
<field eval="time.strftime('%Y-12-01')" name="membership_date_to"/>
<field eval="time.strftime('%Y-12-31')" name="membership_date_to"/>
<field name="name">Basic Membership</field>
<field name="categ_id" ref="product.product_category_1"/>
<field name="list_price">40</field>

View File

@ -29,6 +29,7 @@ from openerp.tools import float_compare
from openerp.tools.translate import _
from openerp import tools, SUPERUSER_ID
from openerp import SUPERUSER_ID
from openerp.addons.product import _common
#----------------------------------------------------------
# Work Centers
@ -320,7 +321,7 @@ class mrp_bom(osv.osv):
"""
routing_obj = self.pool.get('mrp.routing')
factor = factor / (bom.product_efficiency or 1.0)
factor = rounding(factor, bom.product_rounding)
factor = _common.ceiling(factor, bom.product_rounding)
if factor < bom.product_rounding:
factor = bom.product_rounding
result = []
@ -376,6 +377,8 @@ class mrp_bom(osv.osv):
def rounding(f, r):
# TODO for trunk: log deprecation warning
# _logger.warning("Deprecated rounding method, please use tools.float_round to round floats.")
import math
if not r:
return f

View File

@ -57,7 +57,8 @@ The following topics should be covered by this module:
'test/test_mrp_repair_b4inv.yml',
'test/test_mrp_repair_afterinv.yml',
'test/test_mrp_repair_cancel.yml',
'test/mrp_repair_report.yml'
'test/mrp_repair_report.yml',
'test/test_mrp_repair_fee.yml',
],
'installable': True,
'auto_install': False,

View File

@ -108,10 +108,12 @@ class mrp_repair(osv.osv):
return res
def _get_lines(self, cr, uid, ids, context=None):
result = {}
for line in self.pool.get('mrp.repair.line').browse(cr, uid, ids, context=context):
result[line.repair_id.id] = True
return result.keys()
return self.pool['mrp.repair'].search(
cr, uid, [('operations', 'in', ids)], context=context)
def _get_fee_lines(self, cr, uid, ids, context=None):
return self.pool['mrp.repair'].search(
cr, uid, [('fees_lines', 'in', ids)], context=context)
_columns = {
'name': fields.char('Repair Reference',size=24, required=True, states={'confirmed':[('readonly',True)]}),
@ -160,18 +162,21 @@ class mrp_repair(osv.osv):
'repaired': fields.boolean('Repaired', readonly=True),
'amount_untaxed': fields.function(_amount_untaxed, string='Untaxed Amount',
store={
'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations'], 10),
'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations', 'fees_lines'], 10),
'mrp.repair.line': (_get_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
'mrp.repair.fee': (_get_fee_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
}),
'amount_tax': fields.function(_amount_tax, string='Taxes',
store={
'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations'], 10),
'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations', 'fees_lines'], 10),
'mrp.repair.line': (_get_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
'mrp.repair.fee': (_get_fee_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
}),
'amount_total': fields.function(_amount_total, string='Total',
store={
'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations'], 10),
'mrp.repair': (lambda self, cr, uid, ids, c={}: ids, ['operations', 'fees_lines'], 10),
'mrp.repair.line': (_get_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
'mrp.repair.fee': (_get_fee_lines, ['price_unit', 'price_subtotal', 'product_id', 'tax_id', 'product_uom_qty', 'product_uom'], 10),
}),
}

View File

@ -0,0 +1,23 @@
-
Testing total amount update function
-
I check the total amount of mrp_repair_rmrp1 is 100
-
!assert {model: mrp.repair, id: mrp_repair_rmrp1, string=amount_total should be 100}:
- amount_total == 100
-
I add a new fee line
-
!record {model: mrp.repair, id: mrp_repair_rmrp1}:
fees_lines:
- name: 'Assembly Service Cost'
product_id: product.product_assembly
product_uom_qty: 1.0
product_uom: product.product_uom_hour
price_unit: 12.0
to_invoice: True
-
I check the total amount of mrp_repair_rmrp1 is now 112
-
!assert {model: mrp.repair, id: mrp_repair_rmrp1, string=amount_total should be 112}:
- amount_total == 112

View File

@ -275,9 +275,12 @@ class PaymentAcquirer(osv.Model):
</div>""" % (amount, payment_header)
return result % html_block.decode("utf-8")
def render_payment_block(self, cr, uid, reference, amount, currency_id, tx_id=None, partner_id=False, partner_values=None, tx_values=None, context=None):
def render_payment_block(self, cr, uid, reference, amount, currency_id, tx_id=None, partner_id=False, partner_values=None, tx_values=None, company_id=None, context=None):
html_forms = []
acquirer_ids = self.search(cr, uid, [('website_published', '=', True), ('validation', '=', 'automatic')], context=context)
domain = [('website_published', '=', True), ('validation', '=', 'automatic')]
if company_id:
domain.append(('company_id', '=', company_id))
acquirer_ids = self.search(cr, uid, domain, context=context)
for acquirer_id in acquirer_ids:
button = self.render(
cr, uid, acquirer_id,

View File

@ -9,14 +9,11 @@ class AccountPaymentConfig(osv.TransientModel):
_columns = {
'module_payment_paypal': fields.boolean(
'Manage Payments Using Paypal',
help='Blahblahblah\n'
'-It installs the module payment_paypal.'),
help='-It installs the module payment_paypal.'),
'module_payment_ogone': fields.boolean(
'Manage Payments Using Ogone',
help='Blahblahblah\n'
'-It installs the module payment_ogone.'),
help='-It installs the module payment_ogone.'),
'module_payment_adyen': fields.boolean(
'Manage Payments Using Adyen',
help='Blahblahblah\n'
'-It installs the module payment_adyen.'),
help='-It installs the module payment_adyen.'),
}

View File

@ -30,10 +30,10 @@ class PaymentAcquirerOgone(osv.Model):
@TDETODO: complete me
"""
return {
'ogone_standard_order_url': 'https://secure.ogone.com/ncol/%s/orderstandard.asp' % env,
'ogone_direct_order_url': 'https://secure.ogone.com/ncol/%s/orderdirect.asp' % env,
'ogone_direct_query_url': 'https://secure.ogone.com/ncol/%s/querydirect.asp' % env,
'ogone_afu_agree_url': 'https://secure.ogone.com/ncol/%s/AFU_agree.asp' % env,
'ogone_standard_order_url': 'https://secure.ogone.com/ncol/%s/orderstandard_utf8.asp' % (env,),
'ogone_direct_order_url': 'https://secure.ogone.com/ncol/%s/orderdirect_utf8.asp' % (env,),
'ogone_direct_query_url': 'https://secure.ogone.com/ncol/%s/querydirect_utf8.asp' % (env,),
'ogone_afu_agree_url': 'https://secure.ogone.com/ncol/%s/AFU_agree.asp' % (env,),
}
_columns = {

View File

@ -70,6 +70,7 @@ class AcquirerPaypal(osv.Model):
else:
paypal_view = self.pool['ir.model.data'].get_object(cr, uid, 'payment_paypal', 'paypal_acquirer_button')
self.create(cr, uid, {
'name': 'paypal',
'paypal_email_account': company_paypal_account,
'view_template_id': paypal_view.id,
}, context=context)

View File

@ -8,7 +8,7 @@
<field name="inherit_id" ref="account.view_account_config_settings"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='payment_acquirer']" version="7.0" position="inside">
<button name='%(payment.acquirer_list)d' type="action"
<button name='%(payment.action_payment_acquirer)d' type="action"
string="Configure payment acquiring methods" class="oe_link"/>
</xpath>
</field>

View File

@ -8,7 +8,7 @@ import random
from openerp import http
from openerp.http import request
from openerp.addons.web.controllers.main import manifest_list, module_boot, html_template
from openerp.addons.web.controllers.main import manifest_list, module_boot, html_template, login_redirect
_logger = logging.getLogger(__name__)
@ -59,7 +59,7 @@ class PosController(http.Controller):
def a(self, debug=False, **k):
if not request.session.uid:
return http.local_redirect('/web/login?redirect=/pos/web')
return login_redirect()
js_list = manifest_list('js',db=request.db, debug=debug)
css_list = manifest_list('css',db=request.db, debug=debug)

View File

@ -35,14 +35,14 @@ class mail_message(osv.Model):
"""
if uid == SUPERUSER_ID:
return super(mail_message, self)._search(cr, uid, args, offset=offset, limit=limit, order=order,
context=context, count=False, access_rights_uid=access_rights_uid)
context=context, count=count, access_rights_uid=access_rights_uid)
group_ids = self.pool.get('res.users').browse(cr, uid, uid, context=context).groups_id
group_user_id = self.pool.get("ir.model.data").get_object_reference(cr, uid, 'base', 'group_user')[1]
if group_user_id not in [group.id for group in group_ids]:
args = [('subtype_id', '!=', False)] + list(args)
return super(mail_message, self)._search(cr, uid, args, offset=offset, limit=limit, order=order,
context=context, count=False, access_rights_uid=access_rights_uid)
context=context, count=count, access_rights_uid=access_rights_uid)
def check_access_rule(self, cr, uid, ids, operation, context=None):
""" Add Access rules of mail.message for non-employee user:

View File

@ -6,12 +6,18 @@
z-index: 0;
}
.openerp .oe_form .oe_form_embedded_html.view_portal_payment_options {
overflow: visible;
}
.openerp .payment_acquirers {
margin: -40px 0 -32px -24px;
position: relative;
padding: 10px 15px;
right: -125px; /* improved margin according bootstrap3 */
width: 650px;
margin-left: 80px;
background: #729FCF;
background-image: -webkit-gradient(linear, left top, left bottom, from(#729FCF), to(#3465A4));
background-image: -webkit-linear-gradient(top, #729FCF, #3465A4);

View File

@ -39,7 +39,7 @@ class sale_order(osv.Model):
if this.state not in ('draft', 'cancel') and not this.invoiced:
result[this.id] = payment_acquirer.render_payment_block(
cr, uid, this.name, this.amount_total, this.pricelist_id.currency_id.id,
partner_id=this.partner_id.id, context=context)
partner_id=this.partner_id.id, company_id=this.company_id.id, context=context)
return result
def action_quotation_send(self, cr, uid, ids, context=None):
@ -90,7 +90,7 @@ class account_invoice(osv.Model):
if this.type == 'out_invoice' and this.state not in ('draft', 'done') and not this.reconciled:
result[this.id] = payment_acquirer.render_payment_block(
cr, uid, this.number, this.residual, this.currency_id.id,
partner_id=this.partner_id.id, context=context)
partner_id=this.partner_id.id, company_id=this.company_id.id, context=context)
return result
def action_invoice_sent(self, cr, uid, ids, context=None):

View File

@ -9,7 +9,7 @@
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<notebook version="7.0" position="before">
<field name="portal_payment_options" groups="portal_sale.group_payment_options"/>
<field name="portal_payment_options" groups="portal_sale.group_payment_options" class="view_portal_payment_options"/>
</notebook>
</field>
</record>
@ -19,7 +19,7 @@
<field name="inherit_id" ref="account.invoice_form"/>
<field name="arch" type="xml">
<notebook version="7.0" position="before">
<field name="portal_payment_options" groups="portal_sale.group_payment_options"/>
<field name="portal_payment_options" groups="portal_sale.group_payment_options" class="view_portal_payment_options"/>
</notebook>
</field>
</record>

View File

@ -11,7 +11,7 @@
<div>
<field name="group_payment_options" class="oe_inline"/>
<label for="group_payment_options"/>
<button name='%(payment.acquirer_list)d' type="action"
<button name='%(payment.action_payment_acquirer)d' type="action"
string="Configure payment acquiring methods" class="oe_link"/>
</div>
</xpath>

View File

@ -222,7 +222,7 @@
<field name="product_id" on_change="onchange_product_id(product_id)"/>
</group>
<group>
<field name="warehouse_id" on_change="onchange_warehouse_id(warehouse_id)" widget="selection" groups="stock.group_locations"/>
<field name="warehouse_id" on_change="onchange_warehouse_id(warehouse_id)" options="{'no_create': True}" groups="stock.group_locations"/>
<field name="product_uom" groups="product.group_uom"/>
<field name="location_id" groups="stock.group_locations"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>

View File

@ -63,7 +63,6 @@ Print product labels with barcode.
],
'test': [
'product_pricelist_demo.yml',
'test/product_uom.yml',
'test/product_pricelist.yml',
],
'installable': True,

View File

@ -18,12 +18,18 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp import tools
import math
def rounding(f, r):
# TODO for trunk: log deprecation warning
# _logger.warning("Deprecated rounding method, please use tools.float_round to round floats.")
return tools.float_round(f, precision_rounding=r)
# TODO for trunk: add rounding method parameter to tools.float_round and use this method as hook
def ceiling(f, r):
if not r:
return f
return round(f / r) * r
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
return math.ceil(f / r) * r

View File

@ -21,8 +21,7 @@
import time
from _common import rounding
from openerp import tools
from openerp.osv import fields, osv
from openerp.tools.translate import _
@ -180,7 +179,8 @@ class product_pricelist(osv.osv):
if ((v.date_start is False) or (v.date_start <= date)) and ((v.date_end is False) or (v.date_end >= date)):
version = v
break
if not version:
raise osv.except_osv(_('Warning!'), _("At least one pricelist has no active version !\nPlease create or activate one."))
categ_ids = {}
for p in products:
categ = p.categ_id
@ -269,7 +269,8 @@ class product_pricelist(osv.osv):
if price is not False:
price_limit = price
price = price * (1.0+(rule.price_discount or 0.0))
price = rounding(price, rule.price_round) #TOFIX: rounding with tools.float_rouding
if rule.price_round:
price = tools.float_round(price, precision_rounding=rule.price_round)
price += (rule.price_surcharge or 0.0)
if rule.price_min_margin:
price = max(price, price_limit+rule.price_min_margin)

View File

@ -22,7 +22,7 @@
import math
import re
from _common import rounding
from _common import ceiling
from openerp import SUPERUSER_ID
from openerp import tools
@ -178,7 +178,7 @@ class product_uom(osv.osv):
return qty
amount = qty / from_unit.factor
if to_unit:
amount = rounding(amount * to_unit.factor, to_unit.rounding)
amount = ceiling(amount * to_unit.factor, to_unit.rounding)
return amount
def _compute_price(self, cr, uid, from_uom_id, price, to_uom_id=False):
@ -548,15 +548,10 @@ class product_product(osv.osv):
_product_incoming_qty = _get_product_available_func(('confirmed','waiting','assigned'), ('in',))
def _product_lst_price(self, cr, uid, ids, name, arg, context=None):
res = dict.fromkeys(ids, 0.0)
res = {}
product_uom_obj = self.pool.get('product.uom')
# retrieve pricelist
pricelist = None
if context.get('pricelist'):
pricelist = self.pool['product.pricelist'].browse(cr, uid, context.get('pricelist'), context=context)
base_currency = self.pool['res.users'].browse(cr, uid, uid, context=context).company_id.currency_id
for id in ids:
res.setdefault(id, 0.0)
for product in self.browse(cr, uid, ids, context=context):
if 'uom' in context:
uom = product.uos_id or product.uom_id
@ -564,11 +559,7 @@ class product_product(osv.osv):
uom.id, product.list_price, context['uom'])
else:
res[product.id] = product.list_price
res[product.id] = (res[product.id] or 0.0) * (product.price_margin or 1.0) + product.price_extra
# update the result, according to the eventual pricelist currency
if pricelist and pricelist.currency_id:
res[product.id] = self.pool['res.currency'].compute(
cr, uid, base_currency.id, pricelist.currency_id.id, res[product.id], round=False, context=context)
res[product.id] = (res[product.id] or 0.0) * (product.price_margin or 1.0) + product.price_extra
return res
def _save_product_lst_price(self, cr, uid, product_id, field_name, field_value, arg, context=None):

View File

@ -221,7 +221,6 @@
<kanban>
<field name="color"/>
<field name="type"/>
<field name="image_small"/>
<field name="list_price"/>
<templates>
<t t-name="kanban-box">

View File

@ -1,15 +0,0 @@
-
In order to test conversation of UOM,
-
I convert Grams into TON with price.
-
!python {model: product.uom}: |
from_uom_id = ref("product_uom_gram")
to_uom_id = ref("product_uom_ton")
price = 2
qty = 1020000
price = self._compute_price(cr, uid, from_uom_id, price, to_uom_id)
qty = self._compute_qty(cr, uid, from_uom_id, qty, to_uom_id)
assert qty == 1.02, "Qty is not correspond."
assert price == 2000000.0, "Price is not correspond."

View File

@ -0,0 +1,5 @@
from . import test_uom
fast_suite = [
test_uom,
]

View File

@ -0,0 +1,37 @@
from openerp.tests.common import TransactionCase
class TestUom(TransactionCase):
"""Tests for unit of measure conversion"""
def setUp(self):
super(TestUom, self).setUp()
self.product = self.registry('product.product')
self.uom = self.registry('product.uom')
self.imd = self.registry('ir.model.data')
def test_10_conversion(self):
cr, uid = self.cr, self.uid
gram_id = self.imd.get_object_reference(cr, uid, 'product', 'product_uom_gram')[1]
tonne_id = self.imd.get_object_reference(cr, uid, 'product', 'product_uom_ton')[1]
qty = self.uom._compute_qty(cr, uid, gram_id, 1020000, tonne_id)
self.assertEquals(qty, 1.02, "Converted quantity does not correspond.")
price = self.uom._compute_price(cr, uid, gram_id, 2, tonne_id)
self.assertEquals(price, 2000000.0, "Converted price does not correspond.")
def test_20_rounding(self):
cr, uid = self.cr, self.uid
unit_id = self.imd.get_object_reference(cr, uid, 'product', 'product_uom_unit')[1]
categ_unit_id = self.imd.get_object_reference(cr, uid, 'product', 'product_uom_categ_unit')[1]
score_id = self.uom.create(cr, uid, {
'name': 'Score',
'factor_inv': 20,
'uom_type': 'bigger',
'rounding': 1.0,
'category_id': categ_unit_id
})
qty = self.uom._compute_qty(cr, uid, unit_id, 2, score_id)
self.assertEquals(qty, 1, "Converted quantity should be rounded up.")

View File

@ -53,9 +53,9 @@ class product_product(osv.osv):
states = ('draft', 'open', 'paid')
sqlstr="""select
sum(l.price_unit * l.quantity)/sum(l.quantity) as avg_unit_price,
sum(l.price_unit * l.quantity)/sum(nullif(l.quantity,0)) as avg_unit_price,
sum(l.quantity) as num_qty,
sum(l.quantity * (l.price_subtotal/l.quantity)) as total,
sum(l.quantity * (l.price_subtotal/(nullif(l.quantity,0)))) as total,
sum(l.quantity * pt.list_price) as sale_expected,
sum(l.quantity * pt.standard_price) as normal_cost
from account_invoice_line l

View File

@ -48,8 +48,9 @@ class project_issue(osv.Model):
_mail_post_access = 'read'
_track = {
'stage_id': {
'project_issue.mt_issue_new': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence == 1,
'project_issue.mt_issue_stage': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence != 1,
# this is only an heuristics; depending on your particular stage configuration it may not match all 'new' stages
'project_issue.mt_issue_new': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence <= 1,
'project_issue.mt_issue_stage': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence > 1,
},
'user_id': {
'project_issue.mt_issue_assigned': lambda self, cr, uid, obj, ctx=None: obj.user_id and obj.user_id.id,
@ -74,7 +75,7 @@ class project_issue(osv.Model):
def _get_default_stage_id(self, cr, uid, context=None):
""" Gives default stage_id """
project_id = self._get_default_project_id(cr, uid, context=context)
return self.stage_find(cr, uid, [], project_id, [('sequence', '=', 1)], context=context)
return self.stage_find(cr, uid, [], project_id, [('fold', '=', False)], context=context)
def _resolve_project_id_from_context(self, cr, uid, context=None):
""" Returns ID of project based on the value of 'default_project_id'

View File

@ -200,7 +200,7 @@
<group>
<field name="date_order"/>
<field name="origin" attrs="{'invisible': [('origin','=',False)]}"/>
<field name="warehouse_id" on_change="onchange_warehouse_id(warehouse_id)" widget="selection" groups="stock.group_locations"/>
<field name="warehouse_id" on_change="onchange_warehouse_id(warehouse_id)" options="{'no_create': True}" groups="stock.group_locations"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
</group>
</group>

View File

@ -205,8 +205,8 @@
<field groups="base.group_no_one" name="origin"/>
</group>
<group name="sale_pay">
<field name="payment_term" widget="selection"/>
<field name="fiscal_position" widget="selection"/>
<field name="payment_term" options="{'no_create': True}"/>
<field name="fiscal_position" options="{'no_create': True}"/>
<field name="company_id" widget="selection" groups="base.group_multi_company"/>
</group>
<group>

View File

@ -27,7 +27,7 @@
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<field name="user_id" position="after">
<field name="section_id" widget="selection" groups="base.group_multi_salesteams"/>
<field name="section_id" options="{'no_create': True}" groups="base.group_multi_salesteams"/>
<field name="categ_ids" widget="many2many_tags"/>
</field>
</field>

View File

@ -27,7 +27,7 @@
<field name="company_id" readonly="True"/>
</field>
<field name="fiscal_position" position="after">
<field name="warehouse_id" on_change="onchange_warehouse_id(warehouse_id)" widget="selection" groups="stock.group_locations"/>
<field name="warehouse_id" on_change="onchange_warehouse_id(warehouse_id)" options="{'no_create': True}" groups="stock.group_locations"/>
</field>
<field name="product_id" position="replace">
<field name="product_id"

View File

@ -106,15 +106,16 @@ class stock_fill_inventory(osv.osv_memory):
datas = {}
res[location] = {}
move_ids = move_obj.search(cr, uid, ['|',('location_dest_id','=',location),('location_id','=',location),('state','=','done')], context=context)
local_context = dict(context)
local_context['raise-exception'] = False
for move in move_obj.browse(cr, uid, move_ids, context=context):
lot_id = move.prodlot_id.id
prod_id = move.product_id.id
if move.location_dest_id.id != move.location_id.id:
if move.location_dest_id.id == location:
qty = uom_obj._compute_qty(cr, uid, move.product_uom.id,move.product_qty, move.product_id.uom_id.id)
qty = uom_obj._compute_qty_obj(cr, uid, move.product_uom,move.product_qty, move.product_id.uom_id, context=local_context)
else:
qty = -uom_obj._compute_qty(cr, uid, move.product_uom.id,move.product_qty, move.product_id.uom_id.id)
qty = -uom_obj._compute_qty_obj(cr, uid, move.product_uom,move.product_qty, move.product_id.uom_id, context=local_context)
if datas.get((prod_id, lot_id)):

View File

@ -259,20 +259,18 @@ class Website(openerp.addons.web.controllers.main.Home):
u"Image size excessive, uploaded images must be smaller "
u"than 42 million pixel")
attachment_id = request.registry['ir.attachment'].create(request.cr, request.uid, {
Attachments = request.registry['ir.attachment']
attachment_id = Attachments.create(request.cr, request.uid, {
'name': upload.filename,
'datas': image_data.encode('base64'),
'datas_fname': upload.filename,
'res_model': 'ir.ui.view',
}, request.context)
url = website.urlplus('/website/image', {
'model': 'ir.attachment',
'id': attachment_id,
'field': 'datas',
'max_height': MAX_IMAGE_HEIGHT,
'max_width': MAX_IMAGE_WIDTH,
})
[attachment] = Attachments.read(
request.cr, request.uid, [attachment_id], ['website_url'],
context=request.context)
url = attachment['website_url']
except Exception, e:
logger.exception("Failed to upload image to attachment")
message = unicode(e)

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import copy
import re
import simplejson
import werkzeug
@ -158,6 +159,29 @@ class view(osv.osv):
return super(view, self).render(cr, uid, id_or_xml_id, values=values, engine=engine, context=context)
def _pretty_arch(self, arch):
# remove_blank_string does not seem to work on HTMLParser, and
# pretty-printing with lxml more or less requires stripping
# whitespace: http://lxml.de/FAQ.html#why-doesn-t-the-pretty-print-option-reformat-my-xml-output
# so serialize to XML, parse as XML (remove whitespace) then serialize
# as XML (pretty print)
arch_no_whitespace = etree.fromstring(
etree.tostring(arch, encoding='utf-8'),
parser=etree.XMLParser(encoding='utf-8', remove_blank_text=True))
arch_pretty_indent_2 = etree.tostring(
arch_no_whitespace, encoding='unicode', pretty_print=True)
# pretty_print uses a fixed indent level of 2, we want an indent of 4,
# double up leading spaces.
def repl(m):
indent = len(m.group(0)) / 2
return u' ' * 4 * indent
# FIXME: If py2.7 only, can use re.M in sub and don't have to do replacement line by line
return u'\n'.join(
re.sub(ur'^((?: )+)', repl, line)
for line in arch_pretty_indent_2.split(u'\n')
)
def save(self, cr, uid, res_id, value, xpath=None, context=None):
""" Update a view section. The view section may embed fields to write
@ -183,5 +207,5 @@ class view(osv.osv):
arch = self.replace_arch_section(cr, uid, res_id, xpath, arch_section, context=context)
self.write(cr, uid, res_id, {
'arch': etree.tostring(arch, encoding='utf-8').decode('utf-8')
'arch': self._pretty_arch(arch)
}, context=context)

View File

@ -3,7 +3,7 @@ from openerp.osv import fields, osv
class website_config_settings(osv.osv_memory):
_name = 'website.config.settings'
_inherit = 'base.config.settings'
_inherit = 'res.config.settings'
_columns = {
'website_id': fields.many2one('website', string="website", required=True),

View File

@ -8,6 +8,7 @@ import urlparse
import werkzeug
import werkzeug.exceptions
import werkzeug.utils
import werkzeug.wrappers
# optional python-slugify import (https://github.com/un33k/python-slugify)
try:
@ -566,13 +567,40 @@ class ir_attachment(osv.osv):
'website_url': fields.function(_website_url_get, string="Attachment URL", type='char')
}
def try_remove(self, cr, uid, ids, context=None):
""" Removes a web-based image attachment if it is used by no view
(template)
Returns a dict mapping attachments which would not be removed (if any)
mapped to the views preventing their removal
"""
Views = self.pool['ir.ui.view']
attachments_to_remove = []
# views blocking removal of the attachment
removal_blocked_by = {}
for attachment in self.browse(cr, uid, ids, context=context):
# in-document URLs are html-escaped, a straight search will not
# find them
url = werkzeug.utils.escape(attachment.website_url)
ids = Views.search(cr, uid, [('arch', 'like', url)], context=context)
if ids:
removal_blocked_by[attachment.id] = Views.read(
cr, uid, ids, ['name'], context=context)
else:
attachments_to_remove.append(attachment.id)
if attachments_to_remove:
self.unlink(cr, uid, attachments_to_remove, context=context)
return removal_blocked_by
class res_partner(osv.osv):
_inherit = "res.partner"
def google_map_img(self, cr, uid, ids, zoom=8, width=298, height=298, context=None):
partner = self.browse(cr, uid, ids[0], context=context)
params = {
'center': '%s, %s %s, %s' % (partner.street, partner.city, partner.zip, partner.country_id and partner.country_id.name_get()[0][1] or ''),
'center': '%s, %s %s, %s' % (partner.street or '', partner.city or '', partner.zip or '', partner.country_id and partner.country_id.name_get()[0][1] or ''),
'size': "%sx%s" % (height, width),
'zoom': zoom,
'sensor': 'false',

View File

@ -418,6 +418,17 @@ if (typeof jQuery === "undefined") { throw new Error("Bootstrap requires jQuery"
setTimeout(function () { that.$element.trigger('slid') }, 0)
})
.emulateTransitionEnd(600)
} else if(this.$element.hasClass('slide')) {
this.$element.trigger(e)
if (e.isDefaultPrevented()) return
$active.animate({left: (direction == 'right' ? '100%' : '-100%')}, 600, function(){
$active.removeClass('active')
that.sliding = false
setTimeout(function() { that.$element.trigger('slid')}, 0)
})
$next.addClass(type).css({left: (direction == 'right' ? '-100%' : '100%')}).animate({left: 0}, 600, function() {
$next.removeClass(type).addClass('active')
})
} else {
this.$element.trigger(e)
if (e.isDefaultPrevented()) return

View File

@ -26,6 +26,7 @@
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
-ms-filter: "alpha(opacity=50)";
}
/* ---- OpenERP Style ---- {{{ */
@ -250,6 +251,26 @@ ul.oe_menu_editor .disclose {
.existing-attachments .pager .disabled {
display: none;
}
.existing-attachments .existing-attachment-cell {
position: relative;
}
.existing-attachments .existing-attachment-cell .img {
border: 1px solid #848490;
}
.existing-attachments .existing-attachment-cell .existing-attachment-remove {
position: absolute;
top: 0;
left: 15px;
cursor: pointer;
background: white;
padding: 2px;
border: 1px solid #848490;
border-top: none;
border-left: none;
-moz-border-radius-bottomright: 8px;
-webkit-border-bottom-right-radius: 8px;
border-bottom-right-radius: 8px;
}
.cke_widget_wrapper {
position: static !important;

View File

@ -23,6 +23,7 @@
background: transparent
border: none
+box-shadow(none)
-ms-filter: "alpha(opacity=50)"
// }}}
@ -210,9 +211,27 @@ ul.oe_menu_editor
.font-icons-selected
background-color: #ddd
$attachment-border-color: #848490
.existing-attachments
.pager .disabled
display: none
.existing-attachments .pager .disabled
display: none
.existing-attachment-cell
position: relative
.img
border: 1px solid $attachment-border-color
.existing-attachment-remove
position: absolute
top: 0
left: 15px // padding-left on col-*
cursor: pointer
background: white
padding: 2px
border: 1px solid $attachment-border-color
border-top: none
border-left: none
+border-bottom-right-radius(8px)
// wrapper positioned relatively for drag&drop widget which is disabled below.
// Breaks completely horribly crazy products listing page, so take it out.

View File

@ -80,10 +80,8 @@
-moz-user-select: none;
user-select: none;
cursor: move;
pointer-events: none;
}
.oe_snippet .oe_snippet_thumbnail {
pointer-events: auto;
text-align: center;
height: 100%;
background: transparent;
@ -200,6 +198,7 @@
.oe_overlay {
display: none;
height: 0;
position: absolute;
background: transparent;
-webkit-border-radius: 3px;
@ -214,20 +213,12 @@
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
pointer-events: none;
}
.oe_overlay.oe_active {
display: block;
border-style: dashed;
border-width: 1px;
-webkit-box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.3), 0px 0px 0px 1px rgba(255, 255, 255, 0.3) inset;
-moz-box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.3), 0px 0px 0px 1px rgba(255, 255, 255, 0.3) inset;
box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.3), 0px 0px 0px 1px rgba(255, 255, 255, 0.3) inset;
border-color: rgba(0, 0, 0, 0.5);
}
.oe_overlay .oe_handle {
display: block !important;
pointer-events: auto;
position: absolute;
top: 50%;
left: 50%;
@ -238,7 +229,18 @@
height: 16px;
margin: -2px;
}
.oe_overlay .oe_handle > div {
z-index: 1;
position: absolute;
border-style: dashed;
border-width: 1px;
border-color: #666666;
-webkit-box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.5), 0px 0px 0px 1px rgba(255, 255, 255, 0.5) inset;
-moz-box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.5), 0px 0px 0px 1px rgba(255, 255, 255, 0.5) inset;
box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.5), 0px 0px 0px 1px rgba(255, 255, 255, 0.5) inset;
}
.oe_overlay .oe_handle.e:before, .oe_overlay .oe_handle.w:before, .oe_overlay .oe_handle.s:before, .oe_overlay .oe_handle.n:before, .oe_overlay .oe_handle.size .oe_handle_button {
z-index: 2;
position: relative;
top: 50%;
left: 50%;
@ -270,56 +272,70 @@
-moz-box-shadow: 0 0 5px 3px rgba(255, 255, 255, 0.7);
box-shadow: 0 0 5px 3px rgba(255, 255, 255, 0.7);
}
.oe_overlay .oe_handle.e, .oe_overlay .oe_handle.w {
top: 4px;
height: 100%;
}
.oe_overlay .oe_handle.e:before, .oe_overlay .oe_handle.w:before {
content: "\f0d9-\f0da";
line-height: 16px;
}
.oe_overlay .oe_handle.e > div, .oe_overlay .oe_handle.w > div {
width: 0;
height: 100%;
top: 2px;
left: 8px;
}
.oe_overlay .oe_handle.e {
left: auto;
top: 2px;
height: 100%;
right: -6px;
cursor: w-resize;
}
.oe_overlay .oe_handle.w {
top: 2px;
height: 100%;
left: -6px;
cursor: e-resize;
}
.oe_overlay .oe_handle.s, .oe_overlay .oe_handle.n {
left: 2px;
width: 100%;
}
.oe_overlay .oe_handle.s:before, .oe_overlay .oe_handle.n:before {
z-index: 0;
content: "\f07d";
text-align: center;
padding: 1px;
}
.oe_overlay .oe_handle.s > div, .oe_overlay .oe_handle.n > div {
width: 100%;
height: 0;
top: 7px;
left: 1px;
}
.oe_overlay .oe_handle.s {
top: auto;
left: 2px;
width: 100%;
bottom: -6px;
cursor: n-resize;
}
.oe_overlay .oe_handle.n {
left: 2px;
width: 100%;
top: -6px;
cursor: s-resize;
}
.oe_overlay .oe_handle.n > div {
top: 5px;
}
.oe_overlay .oe_handle.size {
z-index: 3;
top: auto;
left: 2px;
width: 100%;
left: 50%;
bottom: -6px;
}
.oe_overlay .oe_handle.size .oe_handle_button {
z-index: 1;
z-index: 3;
content: "Resize";
width: 64px;
text-align: center;
margin-left: -32px;
margin-top: -10px;
cursor: row-resize;
left: 0px;
top: 9px;
}
.oe_overlay .oe_handle.size .oe_handle_button:hover {
background: rgba(30, 30, 30, 0.8);
@ -328,25 +344,20 @@
-moz-box-shadow: 0 0 5px 3px rgba(255, 255, 255, 0.7);
box-shadow: 0 0 5px 3px rgba(255, 255, 255, 0.7);
}
.oe_overlay .oe_handle.size div {
border-style: dashed;
border-width: 0 0 1px 0;
border-color: rgba(0, 0, 0, 0.5);
position: relative;
top: 8px;
}
.oe_overlay .icon.btn {
display: inline-block;
}
.oe_overlay .oe_overlay_options {
position: absolute;
width: 100%;
left: 50% !important;
text-align: center;
top: -11px;
z-index: 1002;
}
.oe_overlay .oe_overlay_options > .btn-group {
left: -50%;
}
.oe_overlay .oe_overlay_options .btn, .oe_overlay .oe_overlay_options a {
pointer-events: auto;
cursor: pointer;
}
.oe_overlay .oe_overlay_options .dropdown {
@ -505,7 +516,6 @@
box-shadow: 0px 3px 17px rgba(99, 53, 150, 0.59);
}
.oe_snippet_editor .oe_snippet > * {
pointer-events: none;
margin-top: 16px;
line-height: 1em;
zoom: 0.6;

View File

@ -64,9 +64,7 @@
overflow: hidden
+user-select(none)
cursor: move
pointer-events: none
.oe_snippet_thumbnail
pointer-events: auto
text-align: center
height: 100%
background: transparent
@ -150,22 +148,17 @@
.oe_overlay
display: none
height: 0
position: absolute
background: transparent
//@include background-image( repeating-linear-gradient(45deg, rgba(255,255,255,.02) ,rgba(255,255,255,.02) 35px, rgba(0,0,0,.02) 35px, rgba(0,0,0,.02) 75px))
+border-radius(3px)
@include transition(opacity 100ms linear)
+box-sizing(border-box)
pointer-events: none
&.oe_active
display: block
border-style: dashed
border-width: 1px
+box-shadow(0px 0px 0px 1px rgba(255,255,255,0.3), 0px 0px 0px 1px rgba(255,255,255,0.3) inset)
border-color: rgba(0, 0, 0, 0.5)
.oe_handle
display: block !important
pointer-events: auto
position: absolute
top: 50%
left: 50%
@ -173,7 +166,15 @@
width: 16px
height: 16px
margin: -2px
> div
z-index: 1
position: absolute
border-style: dashed
border-width: 1px
border-color: #666666
+box-shadow(0px 0px 0px 1px rgba(255,255,255,0.5), 0px 0px 0px 1px rgba(255,255,255,0.5) inset)
&.e:before, &.w:before, &.s:before, &.n:before, &.size .oe_handle_button
z-index: 2
position: relative
top: 50%
left: 50%
@ -196,72 +197,74 @@
color: #fff
+box-shadow(0 0 5px 3px rgba(255,255,255,.7))
&.e, &.w
top: 4px
height: 100%
&:before
content: "\f0d9-\f0da"
line-height: 16px
> div
width: 0
height: 100%
top: 2px
left: 8px
&.e
left: auto
top: 2px
height: 100%
right: -6px
cursor: w-resize
&.w
top: 2px
height: 100%
left: -6px
cursor: e-resize
&.s, &.n
left: 2px
width: 100%
&:before
z-index: 0
content: "\f07d"
text-align: center
padding: 1px
> div
width: 100%
height: 0
top: 7px
left: 1px
&.s
top: auto
left: 2px
width: 100%
bottom: -6px
cursor: n-resize
&.n
left: 2px
width: 100%
top: -6px
cursor: s-resize
> div
top: 5px
&.size
z-index: 3
top: auto
left: 2px
width: 100%
left: 50%
bottom: -6px
.oe_handle_button
z-index: 1
z-index: 3
content: "Resize"
width: 64px
text-align: center
margin-left: -32px
margin-top: -10px
cursor: row-resize
left: 0px
top: 9px
&:hover
background: rgba(30, 30, 30, .8)
color: #fff
+box-shadow(0 0 5px 3px rgba(255,255,255,.7))
div
border-style: dashed
border-width: 0 0 1px 0
border-color: rgba(0, 0, 0, 0.5)
position: relative
top: 8px
.icon.btn
display: inline-block
.oe_overlay_options
position: absolute
width: 100%
left: 50% !important
text-align: center
top: -11px
z-index: 1002
> .btn-group
left: -50%
.btn, a
pointer-events: auto
cursor: pointer
.dropdown
display: inline-block
@ -392,7 +395,6 @@
border: 2px solid rgb(151, 137, 255)
box-shadow: 0px 3px 17px rgba(99, 53, 150, 0.59)
& > *
pointer-events: none
margin-top: 16px
line-height: 1em
zoom: 0.6

View File

@ -394,22 +394,15 @@ div.carousel[data-snippet-id="slider"] .carousel-indicators .active {
}
.parallax {
position: relative;
background-size: cover;
display: table;
width: 100%;
min-height: 100px;
}
.parallax.oe_small {
min-height: 200px;
}
.parallax.oe_medium {
min-height: 300px;
}
.parallax.oe_big {
min-height: 450px;
}
.parallax > div {
position: relative;
display: table;
width: 100%;
min-height: 200px;
}
.parallax > div > div {
display: table-cell;
vertical-align: middle;
padding: 32px 0;

View File

@ -324,21 +324,16 @@ div.carousel[data-snippet-id="slider"]
background-color: grey
.parallax
position: relative
background-size: cover
display: table
width: 100%
min-height: 100px
&.oe_small
min-height: 200px
&.oe_medium
min-height: 300px
&.oe_big
min-height: 450px
> div
display: table-cell
vertical-align: middle
padding: 32px 0
position: relative
display: table
width: 100%
min-height: 200px
> div
display: table-cell
vertical-align: middle
padding: 32px 0
/* Background */

View File

@ -20,6 +20,10 @@
website.form(this.pathname, 'POST');
});
$(document).on('click', '.cke_editable label', function (ev) {
ev.preventDefault();
});
$(document).on('submit', '.cke_editable form', function (ev) {
// Disable form submition in editable mode
ev.preventDefault();
@ -626,11 +630,11 @@
var $link_button = this.make_hover_button(_t("Change"), function () {
var sel = new CKEDITOR.dom.element(previous);
editor.getSelection().selectElement(sel);
if (previous.tagName.toUpperCase() === 'A') {
link_dialog(editor);
} else if(sel.hasClass('fa')) {
if(sel.hasClass('fa')) {
new website.editor.FontIconsDialog(editor, previous)
.appendTo(document.body);
} else if (previous.tagName.toUpperCase() === 'A') {
link_dialog(editor);
}
$link_button.hide();
previous = null;
@ -839,7 +843,6 @@
document.execCommand("enableInlineTableEditing", false, "false");
} catch (e) {}
// detect & setup any CKEDITOR widget within a newly dropped
// snippet. There does not seem to be a simple way to do it for
// HTML not inserted via ckeditor APIs:
@ -1438,6 +1441,7 @@
this.page += $target.hasClass('previous') ? -1 : 1;
this.display_attachments();
},
'click .existing-attachment-remove': 'try_remove',
}),
init: function (parent) {
this.image = null;
@ -1460,7 +1464,7 @@
kwargs: {
fields: ['name', 'website_url'],
domain: [['res_model', '=', 'ir.ui.view']],
order: 'name',
order: 'id desc',
context: website.get_context(),
}
});
@ -1470,6 +1474,7 @@
this.display_attachments();
},
display_attachments: function () {
this.$('.help-block').empty();
var per_screen = IMAGES_PER_ROW * IMAGES_ROWS;
var from = this.page * per_screen;
@ -1497,6 +1502,34 @@
}
this.close()
},
try_remove: function (e) {
var $help_block = this.$('.help-block').empty();
var self = this;
var id = parseInt($(e.target).data('id'), 10);
var attachment = _.findWhere(this.records, {id: id});
return openerp.jsonRpc('/web/dataset/call_kw', 'call', {
model: 'ir.attachment',
method: 'try_remove',
args: [],
kwargs: {
ids: [id],
context: website.get_context()
}
}).then(function (prevented) {
if (_.isEmpty(prevented)) {
self.records = _.without(self.records, attachment);
self.display_attachments();
return;
}
$help_block.replaceWith(openerp.qweb.render(
'website.editor.dialog.image.existing.error', {
views: prevented[id]
}
));
});
},
});
function get_selected_link(editor) {

View File

@ -64,6 +64,10 @@
start: function () {
this.$target.carousel({interval: 10000});
},
stop: function () {
this.$target.carousel('pause');
this.$target.removeData("bs.carousel");
},
});
website.snippet.animationRegistry.parallax = website.snippet.Animation.extend({

View File

@ -19,13 +19,23 @@
edit: function () {
var self = this;
$("body").off('click');
website.snippet.stop_animation();
window.snippets = this.snippets = new website.snippet.BuildingBlock(this);
this.snippets.appendTo(this.$el);
this.on('rte:ready', this, function () {
self.snippets.$button.removeClass("hidden");
website.snippet.stop_animation();
website.snippet.start_animation();
$(website.snippet.readyAnimation).each(function() {
var animation = $(this).data("snippet-view");
if (animation) {
animation.$target.on('focus', '*', function(){
animation.stop();
});
animation.$target.on('blur', '*', function(){
animation.start();
});
}
});
});
return this._super.apply(this, arguments);
@ -190,13 +200,14 @@
var mt = parseInt($target.css("margin-top") || 0);
var mb = parseInt($target.css("margin-bottom") || 0);
$el.css({
'position': 'absolute',
'width': $target.outerWidth(),
'height': $target.outerHeight() + mt + mb+1,
'top': pos.top - mt,
'top': pos.top - mt - 5,
'left': pos.left
});
$el.find(".oe_handle.size").css("bottom", (mb-7)+'px');
$el.find(">.e,>.w").css({'height': $target.outerHeight() + mt + mb+1});
$el.find(">.s").css({'top': $target.outerHeight() + mt + mb});
$el.find(">.size").css({'top': $target.outerHeight() + mt});
$el.find(">.s,>.n").css({'width': $target.outerWidth()-2});
},
show: function () {
this.$el.removeClass("hidden");
@ -213,8 +224,8 @@
bind_snippet_click_editor: function () {
var self = this;
$(document).on('click', "#wrapwrap", function (event) {
var $target = $(event.srcElement);
$("#wrapwrap").on('click', function (event) {
var $target = $(event.srcElement || event.target);
if (!$target.attr("data-snippet-id")) {
$target = $target.parents("[data-snippet-id]:first");
}
@ -415,7 +426,6 @@
$target.data("overlay").remove();
$target.removeData("overlay");
}
self.create_overlay($target);
$target.find("[data-snippet-id]").each(function () {
var $snippet = $(this);
$snippet.removeData("snippet-editor");
@ -423,10 +433,10 @@
$snippet.data("overlay").remove();
$snippet.removeData("overlay");
}
self.create_overlay($snippet);
});
// end
self.create_overlay($target);
self.make_active($target);
},0);
} else {
@ -571,6 +581,20 @@
var $target = $(this);
if (!$target.data('overlay')) {
var $zone = $(openerp.qweb.render('website.snippet_overlay'));
// fix for pointer-events: none with ie9
if (document.body && document.body.addEventListener) {
$zone.on("click mousedown mousedown", function passThrough(event) {
event.preventDefault();
$target.each(function() {
// check if clicked point (taken from event) is inside element
event.srcElement = this;
$(this).trigger(event.type);
});
return false;
});
}
$zone.appendTo('#oe_manipulators');
$zone.data('target',$target);
$target.data('overlay',$zone);
@ -783,6 +807,7 @@
this.parent = parent;
this.$target = $(dom);
this.$overlay = this.$target.data('overlay');
this.$overlay.find('a[data-toggle="dropdown"]').dropdown();
this.snippet_id = this.$target.data("snippet-id");
this._readXMLData();
this.load_style_options();
@ -1210,7 +1235,6 @@
on_clone: function () {
var $clone = this.$target.clone(false);
var _class = $clone.attr("class").replace(/\s*(col-lg-offset-|col-md-offset-)([0-9-]+)/g, '');
_class += ' col-md-1';
$clone.attr("class", _class);
this.$target.after($clone);
this.hide_remove_button();
@ -1258,6 +1282,11 @@
});
website.snippet.editorRegistry.slider = website.snippet.editorRegistry.resize.extend({
getSize: function () {
this.grid = this._super();
this.grid.size = 8;
return this.grid;
},
drop_and_build_snippet: function() {
var id = $(".carousel").length;
this.id = "myCarousel" + id;
@ -1293,7 +1322,6 @@
this.$editor.find(".js_add").on('click', function () {self.on_add_slide(); return false;});
this.$editor.find(".js_remove").on('click', function () {self.on_remove_slide(); return false;});
this.$target.carousel('pause');
this.rebind_event();
},
on_add_slide: function () {
@ -1346,11 +1374,6 @@
});
website.snippet.editorRegistry.carousel = website.snippet.editorRegistry.slider.extend({
getSize: function () {
this.grid = this._super();
this.grid.size = 8;
return this.grid;
},
clean_for_save: function () {
this._super();
this.$target.css("background-image", "");
@ -1436,7 +1459,10 @@
self.$target.data("snippet-view").set_values();
});
this.$target.attr('contentEditable', 'false');
this.$target.find('> div > .oe_structure').attr('contentEditable', 'true');
this.$target.find('> div > .oe_structure').attr('contentEditable', 'true'); // saas-3 retro-compatibility
this.$target.find('> div > div:not(.oe_structure) > .oe_structure').attr('contentEditable', 'true');
},
scroll: function () {
var self = this;

View File

@ -47,7 +47,7 @@
},
{
waitFor: '.oe_overlay_options .oe_options:visible',
element: '#wrap [data-snippet-id=carousel]:first .carousel-caption',
element: '#wrap [data-snippet-id=carousel]:first .carousel-caption > div',
placement: 'top',
title: _t("Customize banner's text"),
content: _t("Click in the text and start editing it. Click continue once it's done."),
@ -69,10 +69,17 @@
popover: { fixed: true },
},
{
snippet: 'three-columns',
element: 'a[href="#snippet_feature"]',
placement: 'bottom',
title: _t("Feature blocks list"),
content: _t("Click on 'Features' to see the feature blocks list."),
popover: { fixed: true },
},
{
snippet: 'features',
placement: 'bottom',
title: _t("Drag & Drop This Block"),
content: _t("Drag the <em>'Three Columns'</em> block and drop it below the banner."),
content: _t("Drag the <em>'Features'</em> block and drop it below the banner."),
popover: { fixed: true },
},
{

View File

@ -140,8 +140,9 @@
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 class="modal-title">Select a Picture</h3>
</div>
<div class="modal-body">
<div class="modal-body has-error">
<div class="existing-attachments"/>
<div class="help-block"/>
</div>
<div class="modal-footer">
<a href="#" data-dismiss="modal" aria-hidden="true">Discard</a>
@ -157,12 +158,27 @@
<li class="next disabled"><a href="#">Next →</a></li>
</ul>
<div class="row mt16" t-foreach="rows" t-as="row">
<div class="col-sm-2" t-foreach="row" t-as="attachment">
<div class="col-sm-2 existing-attachment-cell"
t-foreach="row" t-as="attachment">
<i class="fa fa-times existing-attachment-remove" t-att-data-id="attachment.id"/>
<img t-att-src="attachment.website_url" t-att-alt="attachment.name" class="img img-responsive"/>
</div>
</div>
</div>
</t>
<t t-name="website.editor.dialog.image.existing.error">
<div class="help-block">
<p>The image could not be deleted because it is used in the
following pages or views:</p>
<ul t-foreach="views" t-as="view">
<li>
<a t-attf-href="/web#model=ir.ui.view&amp;id=#{view.id}">
<t t-esc="view.name"/>
</a> (id <t t-esc="view.id"/>)
</li>
</ul>
</div>
</t>
<t t-name="website.editor.table.panel">
<table class="editorbar-panel">
<tr t-foreach="rows"><td t-foreach="cols">&#8203;</td></tr>

View File

@ -46,11 +46,11 @@
<div t-name="website.snippets.resize" data-snippet-id='resize'>
<!-- custom data for the widget -->
<div class='oe_handles'>
<div class='oe_handle n'></div>
<div class='oe_handle e'></div>
<div class='oe_handle w'></div>
<div class='oe_handle size'><div class="oe_handle_button size">Resize</div><div class="oe_border"/></div>
<div class='oe_handle s'></div>
<div class='oe_handle n'><div></div></div>
<div class='oe_handle e'><div></div></div>
<div class='oe_handle w'><div></div></div>
<div class='oe_handle size'><div class="oe_handle_button size">Resize</div></div>
<div class='oe_handle s'><div></div></div>
</div>
<div class='oe_snippet_thumbnail'>Margin resize</div>
</div>

View File

@ -13,8 +13,8 @@ class TestViewSaving(common.TransactionCase):
def eq(self, a, b):
self.assertEqual(a.tag, b.tag)
self.assertEqual(a.attrib, b.attrib)
self.assertEqual(a.text, b.text)
self.assertEqual(a.tail, b.tail)
self.assertEqual((a.text or '').strip(), (b.text or '').strip())
self.assertEqual((a.tail or '').strip(), (b.tail or '').strip())
for ca, cb in itertools.izip_longest(a, b):
self.eq(ca, cb)

View File

@ -792,7 +792,7 @@
</ul>
</li>
<section class="oe_snippet_body parallax"
style="height: 320px; background-image: url('/website/static/src/img/banner/mountains.jpg')"
style="background-image: url('/website/static/src/img/banner/mountains.jpg')"
data-scroll-background-ratio="0.3">
<div><div class="oe_structure"/></div>
</section>
@ -804,10 +804,9 @@
<span class="oe_snippet_thumbnail_title">Parallax Slider</span>
</div>
<section class="oe_snippet_body parallax" data-snippet-id="parallax"
style="height: 320px; background-image: url('/website/static/src/img/parallax/quote.png')"
style="background-image: url('/website/static/src/img/parallax/quote.png')"
data-scroll-background-ratio="0.3">
<div>
<div class="oe_structure">
<div><div><div class="oe_structure">
<div id="myQuoteCarousel" class="carousel quotecarousel slide mb0" data-snippet-id="slider">
<!-- Indicators -->
<ol class="carousel-indicators mb0">
@ -854,8 +853,7 @@
</div>
</div>
</div>
</div>
</div>
</div></div></div>
</section>
</div>
@ -888,7 +886,7 @@
<li class="dropdown-submenu">
<a tabindex="-1" href="#">Style</a>
<ul class="dropdown-menu">
<li data-class="readable"><a>Readability</a></li>
<li data-class="readable"><a>Narrow</a></li>
</ul>
</li>
</div>

View File

@ -80,8 +80,8 @@
<script type="text/javascript" src="/website/static/src/js/website.js"></script>
<script type="text/javascript" src="/website/static/lib/bootstrap/js/bootstrap.js"></script>
<script t-if="not translatable" type="text/javascript" src="/website/static/src/js/website.snippets.animation.js"></script>
<script type="text/javascript" src="/website/static/lib/bootstrap/js/bootstrap.js"></script>
<t t-raw="head or ''" name='layout_head'/>
</head>
@ -113,7 +113,7 @@
</b>
</a>
<ul class="dropdown-menu js_usermenu" role="menu">
<li><a href="/web" role="menuitem">Administration</a></li>
<li><a href="/web" role="menuitem">My Account</a></li>
<li class="divider"/>
<li><a t-attf-href="/web/session/logout?redirect=/" role="menuitem">Logout</a></li>
</ul>

View File

@ -32,7 +32,7 @@
<div t-attf-class="form-group #{error and 'description' in error and 'has-error' or ''}">
<label class="col-md-3 col-sm-4 control-label" for="description">Your Question</label>
<div class="col-md-7 col-sm-8">
<textarea class="form-control" name="description" style="min-height: 120px" required="True" t-attf-value="#{description or ''}"/>
<textarea class="form-control" name="description" style="min-height: 120px" required="True"><t t-esc="description or ''"/></textarea>
</div>
</div>
<div class="form-group">

View File

@ -11,7 +11,7 @@
<record id="action_open_website" model="ir.actions.act_url">
<field name="name">Website Home</field>
<field name="target">self</field>
<field name="url">/event?tutorial.event=true</field>
<field name="url">/event#tutorial.event=true</field>
</record>
<record id="base.open_menu" model="ir.actions.todo">
<field name="action_id" ref="action_open_website"/>

View File

@ -66,7 +66,7 @@
</t>
</div>
<div>
<i class="fa fa-clock-o"></i> <span t-field="event.date_begin"> </span> <i>to</i> <span t-field="event.date_end"> </span>
<i class="fa fa-clock-o"></i> <span t-field="event.date_begin" t-field-options='{"hide_seconds":"True"}'> </span> <i>to</i> <span t-field="event.date_end" t-field-options='{"hide_seconds":"True"}'> </span>
</div>
<div t-field="event.address_id" t-field-options='{
"widget": "contact",
@ -233,8 +233,8 @@
<div class="container">
<h1 class="text-center" t-field="event.name"></h1>
<h4 class="text-center text-muted">
<i class="fa fa-clock-o"></i> <span t-field="event.date_begin"/> to
<span t-field="event.date_end"/>
<i class="fa fa-clock-o"></i> <span t-field="event.date_begin" t-field-options='{"hide_seconds":"True"}'/> to
<span t-field="event.date_end" t-field-options='{"hide_seconds":"True"}'/>
</h4>
<h4 class="text-center text-muted"
t-field="event.address_id" t-field-options='{
@ -278,7 +278,7 @@
<t t-raw="comment.body"/>
<small class="pull-right muted text-right">
<div t-field="comment.author_id"/>
<div t-field="comment.date"/>
<div t-field="comment.date" t-field-options='{"hide_seconds":"True"}'/>
</small>
</div>
</li>
@ -307,8 +307,8 @@
<h4>When</h4>
</div>
<div class="panel-body">
<i class="fa fa-clock-o"></i> from <span t-field="event.date_begin"> </span><br/>
<i class="fa fa-clock-o"></i> to <span t-field="event.date_end"> </span>
<i class="fa fa-clock-o"></i> From <span t-field="event.date_begin" t-field-options='{"hide_seconds":"True"}'> </span><br/>
<i class="fa fa-clock-o"></i> To <span t-field="event.date_end" t-field-options='{"hide_seconds":"True"}'> </span>
</div>
</div>

View File

@ -39,12 +39,12 @@
{
title: "Order Now",
waitFor: 'select[name="ticket-2"] option:contains(3):selected',
element: 'button.btn-primary:contains("Order Now")',
element: '.btn-primary:contains("Order Now")',
},
{
title: "Complete checkout",
waitFor: '#top_menu .my_cart_quantity:contains(5)',
element: 'form[action="/shop/confirm_order/"] button',
element: 'form[action="/shop/confirm_order/"] .btn:contains("Confirm")',
onload: function (tour) {
if ($("input[name='name']").val() === "")
$("input[name='name']").val("website_sale-test-shoptest");
@ -64,7 +64,7 @@
{
title: "Pay Now",
waitFor: '#payment_method label:has(input:checked):has(img[title="transfer"])',
element: '.oe_sale_acquirer_button button[name="submit"]:visible',
element: '.oe_sale_acquirer_button .btn[name="submit"]:visible',
},
{
title: "finish",

View File

@ -5,6 +5,8 @@ def load_tests(loader, base, _):
{'redirect': '/page/website.homepage'}))
base.addTest(test_ui.WebsiteUiSuite(test_ui.full_path(__file__,'website_event_sale_test.js'),
{'redirect': '/page/website.homepage', 'user': 'demo', 'password': 'demo'}))
base.addTest(test_ui.WebsiteUiSuite(test_ui.full_path(__file__,'website_event_sale_test.js'),
{'path': '/', 'user': None}))
# Test has been commented in SAAS-3 ONLY, it must be activated in trunk.
# Log for test JS has been improved in trunk, so we stop to loss time in saas-3 and debug it directly in trunk.
# Tech Saas & AL agreement
# base.addTest(test_ui.WebsiteUiSuite(test_ui.full_path(__file__,'website_event_sale_test.js'), {'path': '/', 'user': None}))
return base

View File

@ -152,7 +152,7 @@
<t t-esc="speaker.name"/>,
</t>
</li>
<li class="text-muted fa fa-calendar"> <span t-field="track.date"/></li>
<li class="text-muted fa fa-calendar"> <span t-field="track.date" t-field-options='{"hide_seconds":"True"}'/></li>
<li class="text-muted fa fa-map-marker" t-if="track.location_id">
<span t-field="track.location_id"/>
</li>
@ -230,7 +230,7 @@
</div>
<div class="panel-body">
<b>Date</b><br/>
<span t-field="track.date"/><br/>
<span t-field="track.date" t-field-options='{"hide_seconds":"True"}'/><br/>
<b>Duration</b><br/>
<span t-field="track.duration"/> minutes<br/>
<b>Location</b><br/>

View File

@ -86,11 +86,14 @@ class website_hr_recruitment(http.Controller):
value = {
'source_id' : imd.xmlid_to_res_id(cr, SUPERUSER_ID, 'hr_recruitment.source_website_company'),
'name': '%s\'s Application' % post.get('partner_name'),
}
for f in ['phone', 'email_from', 'partner_name', 'description']:
for f in ['email_from', 'partner_name', 'description']:
value[f] = post.get(f)
for f in ['department_id', 'job_id']:
value[f] = int(post.get(f) or 0)
# Retro-compatibility for saas-3. "phone" field should be replace by "partner_phone" in the template in trunk.
value['partner_phone'] = post.pop('phone', False)
applicant_id = request.registry['hr.applicant'].create(cr, SUPERUSER_ID, value, context=context)
if post['ufile']:

View File

@ -51,14 +51,14 @@
<section class="oe_container">
<div class="oe_row">
<h2 class="oe_slogan">Post Your Jobs on Best Job Boards</h2>
<h3 class="oe_slogan">LinkedIn, Monster, Kraigslist, Careerbuilder,...</h3>
<h3 class="oe_slogan">LinkedIn, Monster, Craigslist, Careerbuilder,...</h3>
<div class="oe_span6">
<img class="oe_picture oe_screenshot" src="jobs3.png">
</div>
<div class="oe_span6">
<p class='oe_mt32'>
Connect automatically to most famous job board websites;
linkedIn, Monster, Kraigslist, ... Every job position has a new
linkedIn, Monster, Craigslist, ... Every job position has a new
email address automatically assigned to route applications
automatically to the right job position.
</p><p>

View File

@ -7,7 +7,7 @@ from openerp.addons.web.http import request
class WebsiteEmailDesigner(http.Controller):
@http.route('/website_mail/email_designer/<model("email.template"):template>/', type='http', auth="public", website=True, multilang=True)
@http.route('/website_mail/email_designer/<model("email.template"):template>/', type='http', auth="user", website=True, multilang=True)
def index(self, template, **kw):
values = {
'template': template,
@ -15,6 +15,6 @@ class WebsiteEmailDesigner(http.Controller):
print template
return request.website.render("website_mail.designer_index", values)
@http.route(['/website_mail/snippets'], type='json', auth="public", website=True)
@http.route(['/website_mail/snippets'], type='json', auth="user", website=True)
def snippets(self):
return request.website._render('website_mail.email_designer_snippets')

View File

@ -19,9 +19,6 @@
#
##############################################################################
import lxml
import urlparse
from openerp.osv import osv, fields
from openerp.tools.translate import _
@ -39,52 +36,3 @@ class EmailTemplate(osv.Model):
help='Link to the website',
),
}
def _postprocess_html_replace_links(self, cr, uid, body_html, context=None):
""" Post-processing of body_html. Indeed the content generated by the
website builder contains references to local addresses, for example
for images. This method changes those addresses to absolute addresses. """
html = body_html
if not body_html:
return html
# form a tree
root = lxml.html.fromstring(html)
if not len(root) and root.text is None and root.tail is None:
html = '<div>%s</div>' % html
root = lxml.html.fromstring(html)
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
(base_scheme, base_netloc, bpath, bparams, bquery, bfragment) = urlparse.urlparse(base_url)
def _process_link(url):
new_url = url
(scheme, netloc, path, params, query, fragment) = urlparse.urlparse(url)
if not scheme and not netloc:
new_url = urlparse.urlunparse((base_scheme, base_netloc, path, params, query, fragment))
return new_url
# check all nodes, replace :
# - img src -> check URL
# - a href -> check URL
for node in root.iter():
if node.tag == 'a':
node.set('href', _process_link(node.get('href')))
elif node.tag == 'img' and not node.get('src', 'data').startswith('data'):
node.set('src', _process_link(node.get('src')))
html = lxml.html.tostring(root, pretty_print=False, method='html')
# this is ugly, but lxml/etree tostring want to put everything in a 'div' that breaks the editor -> remove that
if html.startswith('<div>') and html.endswith('</div>'):
html = html[5:-6]
return html
def create(self, cr, uid, values, context=None):
if 'body_html' in values:
values['body_html'] = self._postprocess_html_replace_links(cr, uid, values['body_html'], context=context)
return super(EmailTemplate, self).create(cr, uid, values, context=context)
def write(self, cr, uid, ids, values, context=None):
if 'body_html' in values:
values['body_html'] = self._postprocess_html_replace_links(cr, uid, values['body_html'], context=context)
return super(EmailTemplate, self).write(cr, uid, ids, values, context=context)

View File

@ -54,7 +54,7 @@ class MailMessage(osv.Model):
args = ['&', ('website_published', '=', True)] + list(args)
return super(MailMessage, self)._search(cr, uid, args, offset=offset, limit=limit, order=order,
context=context, count=False, access_rights_uid=access_rights_uid)
context=context, count=count, access_rights_uid=access_rights_uid)
def check_access_rule(self, cr, uid, ids, operation, context=None):
""" Add Access rules of mail.message for non-employee user:

View File

@ -58,14 +58,19 @@ class table_compute(object):
self.table = {}
def _check_place(self, posx, posy, sizex, sizey):
res = True
for y in range(sizey):
for x in range(sizex):
if posx+x>=PPR:
return False
res = False
break
row = self.table.setdefault(posy+y, {})
if row.setdefault(posx+x) is not None:
return False
return True
res = False
break
for x in range(PPR):
self.table[posy+y].setdefault(x, None)
return res
def process(self, products):
# Compute products positions on the grid
@ -106,8 +111,10 @@ class table_compute(object):
for col in range(len(rows)):
cols = rows[col].items()
cols.sort()
rows[col] = map(lambda x: x[1], cols)
return filter(bool, rows)
x += len(cols)
rows[col] = [c for c in map(lambda x: x[1], cols) if c != False]
return rows
class Ecommerce(http.Controller):
@ -181,19 +188,29 @@ class Ecommerce(http.Controller):
return request.redirect(url)
def attributes_to_ids(self, attributes):
obj = request.registry.get('product.attribute.line')
domain = []
def attributes_to_ids(self, cr, uid, attributes):
req = """
SELECT product_tmpl_id as id, count(*) as nb_match
FROM product_attribute_line
WHERE 1!=1
"""
nb = 0
for key_val in attributes:
domain.append(("attribute_id", "=", key_val[0]))
attribute_id = key_val[0]
if isinstance(key_val[1], list):
domain.append(("value", ">=", key_val[1][0]))
domain.append(("value", "<=", key_val[1][1]))
req += " OR ( attribute_id = %s AND value >= %s AND value <= %s)" % \
(attribute_id, key_val[1][0], key_val[1][1])
nb += 1
else:
domain.append(("value_id", "in", key_val[1:]))
att_ids = obj.search(request.cr, request.uid, domain, context=request.context)
att = obj.read(request.cr, request.uid, att_ids, ["product_tmpl_id"], context=request.context)
return [r["product_tmpl_id"][0] for r in att]
for value_id in key_val[1:]:
req += " OR ( attribute_id = %s AND value_id = %s)" % \
(attribute_id, value_id)
nb += 1
req += " GROUP BY product_tmpl_id"
cr.execute(req)
result = cr.fetchall()
return [id for id, nb_match in result if nb_match >= nb]
@http.route(['/shop/pricelist'], type='http', auth="public", website=True, multilang=True)
def shop_promo(self, promo=None, **post):
@ -221,7 +238,7 @@ class Ecommerce(http.Controller):
if filters:
filters = simplejson.loads(filters)
if filters:
ids = self.attributes_to_ids(filters)
ids = self.attributes_to_ids(cr, uid, filters)
domain.append(('id', 'in', ids or [0]))
url = "/shop/"
@ -292,7 +309,7 @@ class Ecommerce(http.Controller):
}
return request.website.render("website_sale.product", values)
@http.route(['/shop/product/comment'], type='http', auth="public", methods=['POST'], website=True)
@http.route(['/shop/product/<int:product_template_id>/comment'], type='http', auth="public", methods=['POST'], website=True)
def product_comment(self, product_template_id, **post):
cr, uid, context = request.cr, request.uid, request.context
if post.get('comment'):
@ -324,7 +341,7 @@ class Ecommerce(http.Controller):
# must have a draft sale order with lines at this point, otherwise reset
order = self.get_order()
if order and order.state != 'draft':
request.registry['website'].sale_reset_order(cr, uid, context=context)
request.registry['website'].ecommerce_reset(cr, uid, context=context)
return request.redirect('/shop/')
self.get_pricelist()
@ -422,12 +439,11 @@ class Ecommerce(http.Controller):
partner = None
public_id = request.registry['website'].get_public_user(cr, uid, context)
if not request.uid == public_id:
if request.uid != public_id:
partner = orm_user.browse(cr, uid, uid, context).partner_id
elif order.partner_id:
domain = [("active", "=", False), ("partner_id", "=", order.partner_id.id)]
user_ids = request.registry['res.users'].search(cr, SUPERUSER_ID, domain, context=context)
if not user_ids or public_id not in user_ids:
public_partner = orm_user.browse(cr, SUPERUSER_ID, public_id, context=context).partner_id.id
if public_partner != order.partner_id.id:
partner = orm_partner.browse(cr, SUPERUSER_ID, order.partner_id.id, context)
if partner:
@ -501,9 +517,8 @@ class Ecommerce(http.Controller):
if request.uid != public_id:
partner_id = orm_user.browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id
elif order.partner_id:
domain = [("active", "=", False), ("partner_id", "=", order.partner_id.id)]
user_ids = request.registry['res.users'].search(cr, SUPERUSER_ID, domain, context=context)
if not user_ids or public_id not in user_ids:
public_partner = orm_user.browse(cr, SUPERUSER_ID, public_id, context=context).partner_id.id
if public_partner != order.partner_id.id:
partner_id = order.partner_id.id
if partner_id:
@ -705,8 +720,8 @@ class Ecommerce(http.Controller):
- UDPATE ME
"""
cr, uid, context = request.cr, request.uid, request.context
email_act = None
sale_order_obj = request.registry['sale.order']
email_act = None
if transaction_id is None:
tx = context.get('website_sale_transaction')
@ -719,8 +734,10 @@ class Ecommerce(http.Controller):
order = request.registry['sale.order'].browse(cr, SUPERUSER_ID, sale_order_id, context=context)
assert order.website_session_id == request.httprequest.session['website_session_id']
if not tx or not order:
if not order:
return request.redirect('/shop/')
elif order.amount_total and not tx:
return request.redirect('/shop/mycart')
if not order.amount_total or tx.state == 'done':
# confirm the quotation
@ -734,6 +751,16 @@ class Ecommerce(http.Controller):
# cancel the quotation
sale_order_obj.action_cancel(cr, SUPERUSER_ID, [order.id], context=request.context)
# send the email
if email_act and email_act.get('context'):
composer_values = {}
email_ctx = email_act['context']
public_id = request.registry['website'].get_public_user(cr, uid, context)
if uid == public_id:
composer_values['email_from'] = request.registry['res.users'].browse(cr, SUPERUSER_ID, public_id, context=context).company_id.email
composer_id = request.registry['mail.compose.message'].create(cr, SUPERUSER_ID, composer_values, context=email_ctx)
request.registry['mail.compose.message'].send_mail(cr, SUPERUSER_ID, [composer_id], context=email_ctx)
# clean context and session, then redirect to the confirmation page
request.registry['website'].ecommerce_reset(cr, uid, context=context)

View File

@ -1,3 +1,4 @@
@charset "utf-8";
/* ---- Default Styles ---- */
.oe_product {
border: 1px solid rgba(100, 100, 100, 0.2);
@ -52,7 +53,7 @@
right: 0;
bottom: 0;
overflow: hidden;
padding: 0 15px;
padding: 0 15px 24px 0;
max-height: 110px;
min-height: 56px;
border-top: 1px solid rgba(255, 255, 255, 0.2);
@ -75,11 +76,6 @@
overflow: hidden;
margin-bottom: 30px;
}
.oe_product .oe_subdescription.oe_shadow {
position: absolute;
opacity: 0.3;
max-height: none;
}
.oe_mycart .input-group-addon {
padding-left: 6px;

View File

@ -47,7 +47,7 @@
right: 0
bottom: 0
overflow: hidden
padding: 0 15px
padding: 0 15px 24px 0
max-height: 110px
min-height: 56px
border-top: 1px solid rgba(255,255,255,0.2)
@ -66,10 +66,6 @@
max-height: 42px
overflow: hidden
margin-bottom: 30px
&.oe_shadow
position: absolute
opacity: .3
max-height: none
.oe_mycart
.input-group-addon

View File

@ -22,11 +22,11 @@
{
title: "click on add to cart",
waitFor: 'input[name="product_id"]:eq(1)[checked]',
element: 'form[action="/shop/add_cart/"] button',
element: 'form[action="/shop/add_cart/"] .btn',
},
{
title: "add suggested",
element: 'form[action="/shop/add_cart/"] button.btn-link:contains("Add to Cart")',
element: 'form[action="/shop/add_cart/"] .btn-link:contains("Add to Cart")',
},
{
title: "add one more iPod",
@ -51,7 +51,7 @@
},
{
title: "test with input error",
element: 'form[action="/shop/confirm_order/"] button',
element: 'form[action="/shop/confirm_order/"] .btn:contains("Confirm")',
onload: function (tour) {
$("input[name='phone']").val("");
},
@ -59,7 +59,7 @@
{
title: "test without input error",
waitFor: 'form[action="/shop/confirm_order/"] .has-error',
element: 'form[action="/shop/confirm_order/"] button',
element: 'form[action="/shop/confirm_order/"] .btn:contains("Confirm")',
onload: function (tour) {
if ($("input[name='name']").val() === "")
$("input[name='name']").val("website_sale-test-shoptest");
@ -79,7 +79,7 @@
{
title: "Pay Now",
waitFor: '#payment_method label:has(input:checked):has(img[title="transfer"])',
element: '.oe_sale_acquirer_button button[name="submit"]:visible',
element: '.oe_sale_acquirer_button .btn[name="submit"]:visible',
},
{
title: "finish",

View File

@ -61,6 +61,10 @@ $(document).ready(function () {
return false;
});
$('.a-submit').on('click', function () {
$(this).closest('form').submit();
});
// change price when they are variants
$('form.js_add_cart_json label').on('mouseup', function (ev) {
ev.preventDefault();
@ -107,6 +111,10 @@ $(document).ready(function () {
$min.val( ui.values[ 0 ] );
$max.val( ui.values[ 1 ] );
$form.submit();
},
slide: function( event, ui ) {
$min.val( ui.values[ 0 ] );
$max.val( ui.values[ 1 ] );
}
});
$min.val( $slider.slider( "values", 0 ) );

View File

@ -7,6 +7,8 @@ def load_tests(loader, base, _):
{'redirect': '/page/website.homepage'}))
base.addTest(test_ui.WebsiteUiSuite(test_ui.full_path(__file__,'website_sale-sale_process-test.js'),
{'redirect': '/page/website.homepage', 'user': 'demo', 'password': 'demo'}))
base.addTest(test_ui.WebsiteUiSuite(test_ui.full_path(__file__,'website_sale-sale_process-test.js'),
{'path': '/', 'user': None}))
# Test has been commented in SAAS-3 ONLY, it must be activated in trunk.
# Log for test JS has been improved in trunk, so we stop to loss time in saas-3 and debug it directly in trunk.
# Tech Saas & AL agreement
# base.addTest(test_ui.WebsiteUiSuite(test_ui.full_path(__file__,'website_sale-sale_process-test.js'), {'path': '/', 'user': None}))
return base

Some files were not shown because too many files have changed in this diff Show More