[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 import tools
from openerp.osv import fields, osv, expression from openerp.osv import fields, osv, expression
from openerp.tools.translate import _ 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 import openerp.addons.decimal_precision as dp
@ -2144,7 +2144,7 @@ class account_tax(osv.osv):
tax_compute_precision = precision tax_compute_precision = precision
if taxes and taxes[0].company_id.tax_calculation_rounding_method == 'round_globally': if taxes and taxes[0].company_id.tax_calculation_rounding_method == 'round_globally':
tax_compute_precision += 5 tax_compute_precision += 5
totalin = totalex = float_round(price_unit * quantity, precision) totalin = totalex = round(price_unit * quantity, precision)
tin = [] tin = []
tex = [] tex = []
for tax in taxes: 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)" 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}" context="{'default_customer': 0, 'search_default_supplier': 1, 'default_supplier': 1}"
domain="[('supplier', '=', True)]"/> domain="[('supplier', '=', True)]"/>
<field name="fiscal_position" widget="selection"/> <field name="fiscal_position" options="{'no_create': True}"/>
<field name="origin"/> <field name="origin"/>
<field name="supplier_invoice_number"/> <field name="supplier_invoice_number"/>
<label for="reference_type"/> <label for="reference_type"/>
@ -183,7 +183,7 @@
<field domain="[('company_id', '=', company_id), ('type', '=', 'payable')]" <field domain="[('company_id', '=', company_id), ('type', '=', 'payable')]"
name="account_id" groups="account.group_account_user"/> name="account_id" groups="account.group_account_user"/>
<field name="journal_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="currency_id" groups="base.group_multi_currency"/>
<field name="check_total" groups="account.group_supplier_inv_check_total"/> <field name="check_total" groups="account.group_supplier_inv_check_total"/>
</group> </group>
@ -253,7 +253,7 @@
<field domain="[('partner_id', '=', partner_id)]" name="partner_bank_id" on_change="onchange_partner_bank(partner_bank_id)"/> <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="user_id" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'account.group_account_invoice']}"/>
<field name="name" invisible="1"/> <field name="name" invisible="1"/>
<field name="payment_term" widget="selection"/> <field name="payment_term" options="{'no_create': True}"/>
</group> </group>
<group> <group>
<field name="move_id" groups="account.group_account_user"/> <field name="move_id" groups="account.group_account_user"/>
@ -324,12 +324,12 @@
context="{'search_default_customer':1, 'show_address': 1}" context="{'search_default_customer':1, 'show_address': 1}"
options='{"always_reload": True}' options='{"always_reload": True}'
domain="[('customer', '=', True)]"/> domain="[('customer', '=', True)]"/>
<field name="fiscal_position" widget="selection" /> <field name="fiscal_position" options="{'no_create': True}" />
</group> </group>
<group> <group>
<field name="date_invoice"/> <field name="date_invoice"/>
<field name="journal_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 domain="[('company_id', '=', company_id),('type','=', 'receivable')]" <field domain="[('company_id', '=', company_id),('type','=', 'receivable')]"
name="account_id" groups="account.group_account_user"/> 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')]}"/> <label for="value_amount" string="Amount To Pay" attrs="{'invisible':[('value','=','balance')]}"/>
<div attrs="{'invisible':[('value','=','balance')]}"> <div attrs="{'invisible':[('value','=','balance')]}">
<field name="value_amount" class="oe_inline"/> <field name="value_amount" class="oe_inline"/>
<label string="%%" class="oe_inline" attrs="{'invisible':['!',('value','=','procent')]}" />
</div> </div>
</group> </group>
<group string="Due Date Computation"> <group string="Due Date Computation">

View File

@ -41,6 +41,23 @@ class account_voucher(osv.osv):
'number': fields.char('Number', size=32), '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): 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 """ """ Inherited - add amount_in_word and allow_check_writting in returned value dictionary """
if not context: 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) 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: if 'value' in default:
amount = 'amount' in default['value'] and default['value']['amount'] or amount amount = 'amount' in default['value'] and default['value']['amount'] or amount
amount_in_word = self._amount_to_text(cr, uid, amount, currency_id, context=context)
# 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)
default['value'].update({'amount_in_word':amount_in_word}) default['value'].update({'amount_in_word':amount_in_word})
if journal_id: if journal_id:
allow_check_writing = self.pool.get('account.journal').browse(cr, uid, journal_id, context=context).allow_check_writing 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 '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): 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"> <para style="terp_default_8">
<font color="white"> </font> <font color="white"> </font>
</para> </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"> <para style="terp_default_9">
<font color="white"> </font> <font color="white"> </font>
</para> </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 # if at least one modification has been found
for model_id, resource_id in lines: for model_id, resource_id in lines:
line_model = pool.get('ir.model').browse(cr, SUPERUSER_ID, model_id).model 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 = { vals = {
'method': method, 'method': method,
'object_id': model_id, 'object_id': model_id,
'user_id': uid, 'user_id': uid,
'res_id': resource_id, 'res_id': resource_id,
'name': name,
} }
if (model_id, resource_id) not in old_values and method not in ('copy', 'read'): 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' # 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' # 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 # (because it could also come with the value 'write' if we are deleting the
# record through a one2many field) # record through a one2many field)
name = old_values[(model_id, resource_id)]['value'].get('name',False)
vals.update({'method': 'unlink'}) 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 # create the audittrail log in super admin mode, only if a change has been detected
if lines[(model_id, resource_id)]: if lines[(model_id, resource_id)]:
log_id = pool.get('audittrail.log').create(cr, SUPERUSER_ID, vals) 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__) _logger = logging.getLogger(__name__)
#---------------------------------------------------------- #----------------------------------------------------------
# helpers # helpers
#---------------------------------------------------------- #----------------------------------------------------------
@ -30,11 +31,15 @@ def fragment_to_query_string(func):
var s = l.search ? (l.search === '?' ? '' : '&') : '?'; var s = l.search ? (l.search === '?' ? '' : '&') : '?';
r = l.pathname + l.search + s + q; r = l.pathname + l.search + s + q;
} }
if (r == l.pathname) {
r = '/';
}
window.location = r; window.location = r;
</script></head><body></body></html>""" </script></head><body></body></html>"""
return func(self, *a, **kw) return func(self, *a, **kw)
return wrapper return wrapper
#---------------------------------------------------------- #----------------------------------------------------------
# Controller # Controller
#---------------------------------------------------------- #----------------------------------------------------------
@ -91,6 +96,7 @@ class OAuthLogin(openerp.addons.web.controllers.main.Home):
return response return response
class OAuthController(http.Controller): class OAuthController(http.Controller):
@http.route('/auth_oauth/signin', type='http', auth='none') @http.route('/auth_oauth/signin', type='http', auth='none')

View File

@ -19,31 +19,31 @@
<t t-foreach="js" t-as="js_file"> <t t-foreach="js" t-as="js_file">
<script type="text/javascript" t-att-src="js_file"></script> <script type="text/javascript" t-att-src="js_file"></script>
</t> </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>
<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"/> <t t-set="reset_without_token" t-value="mode == 'reset' and not token"/>
<form class="oe_signup_form" role="form" method="post"> <form class="oe_signup_form" role="form" method="post">
<t t-call="web.database_select"/> <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"> <div class="form-group field-name" t-if="not reset_without_token">
<label for="name" class="control-label">Your Name</label> <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" <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"/> required="required" t-att-disabled="'disabled' if mode == 'reset' and token else None"
</div> t-att-autofocus="'autofocus' if mode != 'reset' and login else None"/>
<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"/>
</div> </div>
<div class="form-group field-password" t-if="not reset_without_token"> <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: elif field['type'] == 'one2many' and depth:
f['fields'] = self.get_fields( f['fields'] = self.get_fields(
cr, uid, field['relation'], context=context, depth=depth-1) 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) fields.append(f)

View File

@ -223,7 +223,7 @@ class calendar_attendee(osv.Model):
}) })
for attendee in self.browse(cr, uid, ids, context=context): 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) 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) 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 = """ 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 AND part_rel.res_partner_id = %s
""" """
@ -1515,7 +1515,7 @@ class calendar_event(osv.Model):
continue continue
if r['class'] == 'private': if r['class'] == 'private':
for f in r.keys(): 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): if isinstance(r[f], list):
r[f] = [] r[f] = []
else: else:

View File

@ -136,7 +136,7 @@
<strong>${object.event_id.name}</strong> <strong>${object.event_id.name}</strong>
</div> </div>
<div style="height: 50px;text-align: left;font-size : 14px;border-collapse: separate;margin-top:10px"> <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>
<div style="height: auto;margin-left:12px;margin-top:30px;"> <div style="height: auto;margin-left:12px;margin-top:30px;">
<table> <table>
@ -151,50 +151,58 @@
</td> </td>
<td> <td>
<table cellspacing="0" cellpadding="0" border="0" style="margin-top: 15px; margin-left: 10px;font-size: 16px;"> <table cellspacing="0" cellpadding="0" border="0" style="margin-top: 15px; margin-left: 10px;font-size: 16px;">
% if object.event_id.location: <tr>
<tr style=" height: 30px;">
<td style="vertical-align:top;"> <td style="vertical-align:top;">
<div style="height: 25px; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;"> % if object.event_id.location:
Where <div style="width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
</div> Where
</td> </div>
<td colspan="1" style="vertical-align:top;"> % endif
<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>
</td> </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> </tr>
% endif
% if object.event_id.description : <tr>
<tr style=" height:auto;">
<td style="vertical-align:top;"> <td style="vertical-align:top;">
<div style="height:auto; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;"> % if object.event_id.description :
What <div style="width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
</div> What
</div>
% endif
</td> </td>
<td colspan="3" style="vertical-align:text-top;"> <td style="vertical-align:text-top;">
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;"> % if object.event_id.description :
: ${object.event_id.description} <div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
</div> : ${object.event_id.description}
</div>
% endif
</td> </td>
</tr> </tr>
% endif
% if not object.event_id.allday and object.event_id.duration: <tr>
<tr style=" height:auto;">
<td style="vertical-align:top;"> <td style="vertical-align:top;">
<div style="height:auto; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;"> % if not object.event_id.allday and object.event_id.duration:
Duration <div style="height:auto; width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
</div> Duration
</div>
% endif
</td> </td>
<td colspan="3" style="vertical-align:text-top;"> <td colspan="3" style="vertical-align:text-top;">
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;"> % if not object.event_id.allday and object.event_id.duration:
: ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))} <div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
</div> : ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))}
</div>
% endif
</td> </td>
</tr> </tr>
% endif
<tr style=" height: 30px;"> <tr style=" height: 30px;">
<td style="height: 25px;width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;"> <td style="height: 25px;width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
<div> <div>
@ -219,9 +227,9 @@
</table> </table>
</div> </div>
<div style="height: auto;width:450px; margin:0 auto;padding-top:20px;padding-bottom:40px;"> <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:#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="${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:#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="${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:#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>
</div> </div>
</body> </body>
@ -259,13 +267,13 @@
<strong>${object.event_id.name}</strong> <strong>${object.event_id.name}</strong>
</div> </div>
<div style="height: 50px;text-align: left;font-size : 14px;border-collapse: separate;margin-top:10px"> <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/> <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> The meeting created by ${object.event_id.user_id.partner_id.name} is now scheduled for : ${object.event_id.display_time}.</p>
</div> </div>
<div style="height: auto;margin-left:12px;margin-top:30px;"> <div style="height: auto;margin-left:12px;margin-top:30px;">
<table> <table>
<tr> <tr>
<td> <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="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;"> <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>
<td> <td>
<table cellspacing="0" cellpadding="0" border="0" style="margin-top: 15px; margin-left: 10px;font-size: 16px;"> <table cellspacing="0" cellpadding="0" border="0" style="margin-top: 15px; margin-left: 10px;font-size: 16px;">
% if object.event_id.location: <tr>
<tr style=" height: 30px;">
<td style="vertical-align:top;"> <td style="vertical-align:top;">
<div style="height: 25px; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;"> % if object.event_id.location:
Where <div style="width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
</div> Where
</td> </div>
<td colspan="1" style="vertical-align:top;"> % endif
<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>
</td> </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> </tr>
% endif
% if object.event_id.description : <tr>
<tr style=" height:auto;">
<td style="vertical-align:top;"> <td style="vertical-align:top;">
<div style="height:auto; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;"> % if object.event_id.description :
What <div style="width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
</div> What
</div>
% endif
</td> </td>
<td colspan="3" style="vertical-align:text-top;"> <td style="vertical-align:text-top;">
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;"> % if object.event_id.description :
: ${object.event_id.description} <div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
</div> : ${object.event_id.description}
</div>
% endif
</td> </td>
</tr> </tr>
% endif
% if not object.event_id.allday and object.event_id.duration: <tr>
<tr style=" height:auto;">
<td style="vertical-align:top;"> <td style="vertical-align:top;">
<div style="height:auto; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;"> % if not object.event_id.allday and object.event_id.duration:
Duration <div style="height:auto; width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
</div> Duration
</div>
% endif
</td> </td>
<td colspan="3" style="vertical-align:text-top;"> <td colspan="3" style="vertical-align:text-top;">
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;"> % if not object.event_id.allday and object.event_id.duration:
: ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))} <div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
</div> : ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))}
</div>
% endif
</td> </td>
</tr> </tr>
% endif
<tr style=" height: 30px;"> <tr style=" height: 30px;">
<td style="height: 25px;width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;"> <td style="height: 25px;width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
<div> <div>
@ -341,12 +357,12 @@
</table> </table>
</td> </td>
</tr> </tr>
</table> </table>
</div> </div>
<div style="height: auto;width:450px; margin:0 auto;padding-top:20px;padding-bottom:40px;"> <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:#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="${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:#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="${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:#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>
</div> </div>
</body> </body>
@ -384,8 +400,8 @@
<strong>${object.event_id.name}</strong> <strong>${object.event_id.name}</strong>
</div> </div>
<div style="height: 50px;text-align: left;font-size : 14px;border-collapse: separate;margin-top:10px"> <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">this it a rmeinder for the event below : </p> <p style="margin-left:12px">this it a reminder for the event below : </p>
</div> </div>
<div style="height: auto;margin-left:12px;margin-top:30px;"> <div style="height: auto;margin-left:12px;margin-top:30px;">
<table> <table>
@ -400,50 +416,58 @@
</td> </td>
<td> <td>
<table cellspacing="0" cellpadding="0" border="0" style="margin-top: 15px; margin-left: 10px;font-size: 16px;"> <table cellspacing="0" cellpadding="0" border="0" style="margin-top: 15px; margin-left: 10px;font-size: 16px;">
% if object.event_id.location: <tr>
<tr style=" height: 30px;">
<td style="vertical-align:top;"> <td style="vertical-align:top;">
<div style="height: 25px; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;"> % if object.event_id.location:
Where <div style="width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
</div> Where
</td> </div>
<td colspan="1" style="vertical-align:top;"> % endif
<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>
</td> </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> </tr>
% endif
% if object.event_id.description : <tr>
<tr style=" height:auto;">
<td style="vertical-align:top;"> <td style="vertical-align:top;">
<div style="height:auto; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;"> % if object.event_id.description :
What <div style="width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
</div> What
</div>
% endif
</td> </td>
<td colspan="3" style="vertical-align:text-top;"> <td style="vertical-align:text-top;">
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;"> % if object.event_id.description :
: ${object.event_id.description} <div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
</div> : ${object.event_id.description}
</div>
% endif
</td> </td>
</tr> </tr>
% endif
% if not object.event_id.allday and object.event_id.duration: <tr>
<tr style=" height:auto;">
<td style="vertical-align:top;"> <td style="vertical-align:top;">
<div style="height:auto; width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;"> % if not object.event_id.allday and object.event_id.duration:
Duration <div style="height:auto; width: 120px; background : #CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
</div> Duration
</div>
% endif
</td> </td>
<td colspan="3" style="vertical-align:text-top;"> <td colspan="3" style="vertical-align:text-top;">
<div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;"> % if not object.event_id.allday and object.event_id.duration:
: ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))} <div style="font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
</div> : ${('%dH%02d' % (object.event_id.duration,(object.event_id.duration*60)%60))}
</div>
% endif
</td> </td>
</tr> </tr>
% endif
<tr style=" height: 30px;"> <tr style=" height: 30px;">
<td style="height: 25px;width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;"> <td style="height: 25px;width: 120px; background : # CCCCCC; font-family: Lucica Grande', Ubuntu, Arial, Verdana, sans-serif;">
<div> <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) stage = self.pool.get('crm.case.stage').browse(cr, uid, stage_id, context=context)
if not stage.on_change: if not stage.on_change:
return {'value': {}} 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): def on_change_partner_id(self, cr, uid, ids, partner_id, context=None):
values = {} values = {}

View File

@ -58,7 +58,7 @@
<field name="view_mode">tree,calendar</field> <field name="view_mode">tree,calendar</field>
<field name="view_id" ref="crm_case_inbound_phone_tree_view"/> <field name="view_id" ref="crm_case_inbound_phone_tree_view"/>
<field name="domain">[]</field> <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="search_view_id" ref="crm.view_crm_case_phonecalls_filter"/>
<field name="help" type="html"> <field name="help" type="html">
<p class="oe_view_nocontent_create"> <p class="oe_view_nocontent_create">

View File

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

View File

@ -65,7 +65,7 @@
Salesman create a mass convert wizard and convert all the leads. Salesman create a mass convert wizard and convert all the leads.
- -
!python {model: crm.lead2opportunity.partner.mass}: | !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) 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) self.mass_convert(cr, uid, [id], context=context)
- -

View File

@ -46,12 +46,12 @@ class crm_lead2opportunity_partner(osv.osv_memory):
results = [] results = []
if partner_id: if partner_id:
# Search for opportunities that have the same partner and that arent done or cancelled # 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: for id in ids:
results.append(id) results.append(id)
email = re.findall(r'([^ ,<@]+@[^> ,]+)', email or '') email = re.findall(r'([^ ,<@]+@[^> ,]+)', email or '')
if email: 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: for id in ids:
results.append(id) results.append(id)
return list(set(results)) return list(set(results))
@ -69,11 +69,10 @@ class crm_lead2opportunity_partner(osv.osv_memory):
if context.get('active_id'): if context.get('active_id'):
tomerge = [int(context['active_id'])] tomerge = [int(context['active_id'])]
email = False
partner_id = res.get('partner_id') partner_id = res.get('partner_id')
lead = lead_obj.browse(cr, uid, int(context['active_id']), context=context) 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.extend(self._get_duplicated_leads(cr, uid, partner_id, email))
tomerge = list(set(tomerge)) tomerge = list(set(tomerge))
@ -100,10 +99,8 @@ class crm_lead2opportunity_partner(osv.osv_memory):
else: else:
user_in_section = False user_in_section = False
if not user_in_section: if not user_in_section:
section_id = False result = self.pool['crm.lead'].on_change_user(cr, uid, ids, user_id, context=context)
section_ids = self.pool.get('crm.case.section').search(cr, uid, ['|', ('user_id', '=', user_id), ('member_ids', '=', user_id)], context=context) section_id = result.get('value') and result['value'].get('section_id') and result['value']['section_id'] or False
if section_ids:
section_id = section_ids[0]
return {'value': {'section_id': section_id}} return {'value': {'section_id': section_id}}
def view_init(self, cr, uid, fields, context=None): 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', []) lead_ids = vals.get('lead_ids', [])
team_id = vals.get('section_id', False) team_id = vals.get('section_id', False)
data = self.browse(cr, uid, ids, context=context)[0] data = self.browse(cr, uid, ids, context=context)[0]
for lead_id in lead_ids: leads = lead.browse(cr, uid, lead_ids, context=context)
partner_id = self._create_partner(cr, uid, lead_id, data.action, data.partner_id.id, context=context) for lead_id in leads:
# FIXME: cannot pass user_ids as the salesman allocation only works in batch 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], partner_id, [], team_id, context=context) res = lead.convert_opportunity(cr, uid, [lead_id.id], partner_id, [], False, context=context)
# FIXME: must perform salesman allocation in batch separately here
user_ids = vals.get('user_ids', False) 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: 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 return res
def action_apply(self, cr, uid, ids, context=None): def action_apply(self, cr, uid, ids, context=None):
@ -144,15 +144,19 @@ class crm_lead2opportunity_partner(osv.osv_memory):
if context is None: if context is None:
context = {} context = {}
lead_obj = self.pool['crm.lead']
w = self.browse(cr, uid, ids, context=context)[0] w = self.browse(cr, uid, ids, context=context)[0]
opp_ids = [o.id for o in w.opportunity_ids] opp_ids = [o.id for o in w.opportunity_ids]
if w.name == 'merge': 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_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": if lead['type'] == "lead":
context.update({'active_ids': lead_ids}) 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) 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: else:
lead_ids = context.get('active_ids', []) 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) 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 = { _columns = {
'user_ids': fields.many2many('res.users', string='Salesmen'), 'user_ids': fields.many2many('res.users', string='Salesmen'),
'section_id': fields.many2one('crm.case.section', 'Sales Team'), '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([ 'action': fields.selection([
('each_exist_or_create', 'Use existing partner or create'), ('each_exist_or_create', 'Use existing partner or create'),
('nothing', 'Do not link to a customer') ('nothing', 'Do not link to a customer')
], 'Related Customer', required=True), ], '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 = { _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.difference(merged_lead_ids)
active_ids = active_ids.union(remaining_lead_ids) active_ids = active_ids.union(remaining_lead_ids)
ctx['active_ids'] = list(active_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) return self.action_apply(cr, uid, ids, context=ctx)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -58,6 +58,8 @@
<group string="Assign opportunities to"> <group string="Assign opportunities to">
<field name="section_id" groups="base.group_multi_salesteams"/> <field name="section_id" groups="base.group_multi_salesteams"/>
<field name="user_ids" widget="many2many_tags"/> <field name="user_ids" widget="many2many_tags"/>
<!-- Uncomment me in trunk -->
<!-- <field name="force_assignation" /> -->
</group> </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)]}"/> <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)]}"> <group attrs="{'invisible': [('deduplicate', '=', False)]}">

View File

@ -9,7 +9,7 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Merge Leads/Opportunities" version="7.0"> <form string="Merge Leads/Opportunities" version="7.0">
<group string="Assign opportunities to"> <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"/> <field name="section_id" class="oe_inline"/>
</group> </group>
<group string="Select Leads/Opportunities"> <group string="Select Leads/Opportunities">

View File

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

View File

@ -24,6 +24,8 @@ import base64
import datetime import datetime
import dateutil.relativedelta as relativedelta import dateutil.relativedelta as relativedelta
import logging import logging
import lxml
import urlparse
import openerp import openerp
from openerp import SUPERUSER_ID from openerp import SUPERUSER_ID
@ -61,6 +63,15 @@ try:
'quote': quote, 'quote': quote,
'urlencode': urlencode, 'urlencode': urlencode,
'datetime': datetime, '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 # dateutil.relativedelta is an old-style class and cannot be directly
# instanciated wihtin a jinja2 expression, so a lambda "proxy" is # instanciated wihtin a jinja2 expression, so a lambda "proxy" is
@ -70,6 +81,7 @@ try:
except ImportError: except ImportError:
_logger.warning("jinja2 not available, templating features will not work!") _logger.warning("jinja2 not available, templating features will not work!")
class email_template(osv.osv): class email_template(osv.osv):
"Templates for sending email" "Templates for sending email"
_name = "email.template" _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] res['model_id'] = self.pool['ir.model'].search(cr, uid, [('model', '=', res.pop('model'))], context=context)[0]
return res 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}`` """Render the given template text, replace mako expressions ``${expr}``
with the result of evaluating these expressions with with the result of evaluating these expressions with
an evaluation context containing: an evaluation context containing:
@ -125,6 +178,10 @@ class email_template(osv.osv):
if render_result == u"False": if render_result == u"False":
render_result = u"" render_result = u""
results[res_id] = render_result 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 return results
def get_email_template_batch(self, cr, uid, template_id=False, res_ids=None, context=None): 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, '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 " help="Optional preferred server for outgoing mails. If not set, the highest "
"priority one will be used."), "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, 'report_name': fields.char('Report Filename', translate=True,
help="Name to use for the generated report file (may contain placeholders)\n" 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."), "The extension can be omitted and will then come from the report type."),
@ -356,17 +413,20 @@ class email_template(osv.osv):
results = dict() results = dict()
for template, template_res_ids in templates_to_res_ids.iteritems(): for template, template_res_ids in templates_to_res_ids.iteritems():
# generate fields value for all res_ids linked to the current template # 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']: for field in fields:
generated_field_values = self.render_template_batch(cr, uid, getattr(template, field), template.model, template_res_ids, context=context) 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(): for res_id, field_value in generated_field_values.iteritems():
results.setdefault(res_id, dict())[field] = field_value results.setdefault(res_id, dict())[field] = field_value
# update values for all res_ids # update values for all res_ids
for res_id in template_res_ids: for res_id in template_res_ids:
values = results[res_id] 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 signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
values['body_html'] = tools.append_content_to_html(values['body_html'], 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['body'] = tools.html_sanitize(values['body_html'])
values.update( values.update(
mail_server_id=template.mail_server_id.id or False, 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) partner_ids += self.pool['res.partner'].exists(cr, SUPERUSER_ID, tpl_partner_ids, context=context)
return partner_ids 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 """ Call email_template.generate_email(), get fields relevant for
mail.compose.message, transform email_cc and email_to into partner_ids """ mail.compose.message, transform email_cc and email_to into partner_ids """
# filter template values # 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) 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: 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', '') res_id_values['body'] = res_id_values.pop('body_html', '')
# transform email_to, email_cc into partner_ids # transform email_to, email_cc into partner_ids
@ -189,7 +191,10 @@ class mail_compose_message(osv.TransientModel):
""" Override to handle templates. """ """ Override to handle templates. """
# generate template-based values # generate template-based values
if wizard.template_id: 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: else:
template_values = dict.fromkeys(res_ids, dict()) template_values = dict.fromkeys(res_ids, dict())
# generate composer values # generate composer values
@ -206,8 +211,8 @@ class mail_compose_message(osv.TransientModel):
template_values[res_id].update(composer_values[res_id]) template_values[res_id].update(composer_values[res_id])
return template_values return template_values
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):
return self.pool.get('email.template').render_template_batch(cr, uid, template, model, res_ids, context=context) return self.pool.get('email.template').render_template_batch(cr, uid, template, model, res_ids, context=context, post_process=post_process)
# Compatibility methods # Compatibility methods
def generate_email_for_composer(self, cr, uid, template_id, res_id, context=None): 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 # TOFIX this request won't select events that have no registration
cr.execute(""" CREATE VIEW report_event_registration AS ( cr.execute(""" CREATE VIEW report_event_registration AS (
SELECT 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.id AS event_id,
e.user_id AS user_id, e.user_id AS user_id,
r.user_id AS user_id_registration, r.user_id AS user_id_registration,

View File

@ -35,7 +35,6 @@
<group expand="1" string="Group By..."> <group expand="1" string="Group By...">
<filter string="Participant / Contact" icon="terp-personal" context="{'group_by':'name_registration'}" help="Registration contact"/> <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="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 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" 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'}"/> <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 #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) 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) 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) self.write(cr, uid, ids, {'account_move_id': move_id, 'state': 'done'}, context=context)
return True return True

View File

@ -23,7 +23,7 @@ import time
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from openerp.osv import fields, osv from openerp.osv import fields, osv
from openerp.tools import config from openerp.tools import config, float_compare
from openerp.tools.translate import _ from openerp.tools.translate import _
class hr_payslip(osv.osv): class hr_payslip(osv.osv):
@ -86,6 +86,7 @@ class hr_payslip(osv.osv):
def process_sheet(self, cr, uid, ids, context=None): def process_sheet(self, cr, uid, ids, context=None):
move_pool = self.pool.get('account.move') move_pool = self.pool.get('account.move')
period_pool = self.pool.get('account.period') period_pool = self.pool.get('account.period')
precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Payroll')
timenow = time.strftime('%Y-%m-%d') timenow = time.strftime('%Y-%m-%d')
for slip in self.browse(cr, uid, ids, context=context): for slip in self.browse(cr, uid, ids, context=context):
@ -149,7 +150,7 @@ class hr_payslip(osv.osv):
line_ids.append(credit_line) line_ids.append(credit_line)
credit_sum += credit_line[2]['credit'] - credit_line[2]['debit'] 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 acc_id = slip.journal_id.default_credit_account_id.id
if not acc_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)) 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) 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 acc_id = slip.journal_id.default_debit_account_id.id
if not acc_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)) 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): 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_template = self.pool.get('account.tax.template')
obj_tax = self.pool.get('account.tax') 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') 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) 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): def get_attachment_type(self, cr, uid, ids, name, args, context=None):
result = {} result = {}
for attachment in self.browse(cr, uid, ids, context=context): for attachment in self.browse(cr, uid, ids, context=context):
fileext = os.path.splitext(attachment.datas_fname)[1].lower() fileext = os.path.splitext(attachment.datas_fname or '')[1].lower()[1:]
if not fileext or not fileext[1:] in self._fileext_to_type: result[attachment.id] = self._fileext_to_type.get(fileext, 'unknown')
return 'unknown'
result[attachment.id] = self._fileext_to_type[fileext[1:]]
return result return result
_columns = { _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: elif mail.model and mail.res_id:
fragment.update(model=mail.model, res_id=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 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: else:
return None return None

View File

@ -351,12 +351,12 @@ class mail_message(osv.Model):
partner_tree = dict((partner[0], partner) for partner in partners) 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 # 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'], { attachments_tree = dict((attachment['id'], {
'id': attachment['id'], 'id': attachment['id'],
'filename': attachment['datas_fname'], 'filename': attachment['datas_fname'],
'name': attachment['name'], 'name': attachment['name'],
'file_type': attachment['file_type'], 'file_type_icon': attachment['file_type_icon'],
}) for attachment in attachments) }) for attachment in attachments)
# 3. Update message dictionaries # 3. Update message dictionaries

View File

@ -88,10 +88,10 @@
--> -->
<t t-name="mail.thread.message.attachments"> <t t-name="mail.thread.message.attachments">
<t t-foreach='widget.attachment_ids' t-as='attachment'> <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' : ''}"> <div t-attf-class="oe_attachment #{attachment.upload ? 'oe_uploading' : ''}">
<a t-att-href='attachment.url' target="_blank"> <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> <div class='oe_name'><t t-raw='attachment.name' /></div>
</a> </a>
<div class='oe_delete oe_e' title="Delete this attachment" t-att-data-id="attachment.id">[</div> <div class='oe_delete oe_e' title="Delete this attachment" t-att-data-id="attachment.id">[</div>
@ -100,7 +100,7 @@
</div> </div>
</div> </div>
</t> </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' : ''}"> <div t-attf-class="oe_attachment oe_preview #{attachment.upload ? 'oe_uploading' : ''}">
<a t-att-href='attachment.url' target="_blank"> <a t-att-href='attachment.url' target="_blank">
<img t-att-src="widget.attachments_resize_image(attachment.id, [100,80])"></img> <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 :return dict results: for each res_id, the generated template values for
subject, body, email_from and reply_to subject, body, email_from and reply_to
""" """
subjects = self.render_template_batch(cr, uid, wizard.subject, 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) 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) 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) replies_to = self.render_template_batch(cr, uid, wizard.reply_to, wizard.model, res_ids, context=context)
results = dict.fromkeys(res_ids, False) results = dict.fromkeys(res_ids, False)
for res_id in res_ids: for res_id in res_ids:
@ -367,7 +367,7 @@ class mail_compose_message(osv.TransientModel):
} }
return results 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}`` """ Render the given template text, replace mako-like expressions ``${expr}``
with the result of evaluating these expressions with an evaluation context with the result of evaluating these expressions with an evaluation context
containing: containing:

View File

@ -6,7 +6,7 @@
<record id="membership_0" model="product.product"> <record id="membership_0" model="product.product">
<field name="membership">True</field> <field name="membership">True</field>
<field eval="time.strftime('%Y-01-01')" name="membership_date_from"/> <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="name">Gold Membership</field>
<field name="list_price">180</field> <field name="list_price">180</field>
<field name="categ_id" ref="product.product_category_1"/> <field name="categ_id" ref="product.product_category_1"/>
@ -16,7 +16,7 @@
<record id="membership_1" model="product.product"> <record id="membership_1" model="product.product">
<field name="membership">True</field> <field name="membership">True</field>
<field eval="time.strftime('%Y-01-01')" name="membership_date_from"/> <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="name">Silver Membership</field>
<field name="categ_id" ref="product.product_category_1"/> <field name="categ_id" ref="product.product_category_1"/>
<field name="list_price">80</field> <field name="list_price">80</field>
@ -26,7 +26,7 @@
<record id="membership_2" model="product.product"> <record id="membership_2" model="product.product">
<field name="membership">True</field> <field name="membership">True</field>
<field eval="time.strftime('%Y-01-01')" name="membership_date_from"/> <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="name">Basic Membership</field>
<field name="categ_id" ref="product.product_category_1"/> <field name="categ_id" ref="product.product_category_1"/>
<field name="list_price">40</field> <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.tools.translate import _
from openerp import tools, SUPERUSER_ID from openerp import tools, SUPERUSER_ID
from openerp import SUPERUSER_ID from openerp import SUPERUSER_ID
from openerp.addons.product import _common
#---------------------------------------------------------- #----------------------------------------------------------
# Work Centers # Work Centers
@ -320,7 +321,7 @@ class mrp_bom(osv.osv):
""" """
routing_obj = self.pool.get('mrp.routing') routing_obj = self.pool.get('mrp.routing')
factor = factor / (bom.product_efficiency or 1.0) 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: if factor < bom.product_rounding:
factor = bom.product_rounding factor = bom.product_rounding
result = [] result = []
@ -376,6 +377,8 @@ class mrp_bom(osv.osv):
def rounding(f, r): 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 import math
if not r: if not r:
return f 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_b4inv.yml',
'test/test_mrp_repair_afterinv.yml', 'test/test_mrp_repair_afterinv.yml',
'test/test_mrp_repair_cancel.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, 'installable': True,
'auto_install': False, 'auto_install': False,

View File

@ -108,10 +108,12 @@ class mrp_repair(osv.osv):
return res return res
def _get_lines(self, cr, uid, ids, context=None): def _get_lines(self, cr, uid, ids, context=None):
result = {} return self.pool['mrp.repair'].search(
for line in self.pool.get('mrp.repair.line').browse(cr, uid, ids, context=context): cr, uid, [('operations', 'in', ids)], context=context)
result[line.repair_id.id] = True
return result.keys() 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 = { _columns = {
'name': fields.char('Repair Reference',size=24, required=True, states={'confirmed':[('readonly',True)]}), '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), 'repaired': fields.boolean('Repaired', readonly=True),
'amount_untaxed': fields.function(_amount_untaxed, string='Untaxed Amount', 'amount_untaxed': fields.function(_amount_untaxed, string='Untaxed Amount',
store={ 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.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', 'amount_tax': fields.function(_amount_tax, string='Taxes',
store={ 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.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', 'amount_total': fields.function(_amount_total, string='Total',
store={ 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.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) </div>""" % (amount, payment_header)
return result % html_block.decode("utf-8") 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 = [] 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: for acquirer_id in acquirer_ids:
button = self.render( button = self.render(
cr, uid, acquirer_id, cr, uid, acquirer_id,

View File

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

View File

@ -30,10 +30,10 @@ class PaymentAcquirerOgone(osv.Model):
@TDETODO: complete me @TDETODO: complete me
""" """
return { return {
'ogone_standard_order_url': 'https://secure.ogone.com/ncol/%s/orderstandard.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.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.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, 'ogone_afu_agree_url': 'https://secure.ogone.com/ncol/%s/AFU_agree.asp' % (env,),
} }
_columns = { _columns = {

View File

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

View File

@ -8,7 +8,7 @@
<field name="inherit_id" ref="account.view_account_config_settings"/> <field name="inherit_id" ref="account.view_account_config_settings"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//div[@name='payment_acquirer']" version="7.0" position="inside"> <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"/> string="Configure payment acquiring methods" class="oe_link"/>
</xpath> </xpath>
</field> </field>

View File

@ -8,7 +8,7 @@ import random
from openerp import http from openerp import http
from openerp.http import request 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__) _logger = logging.getLogger(__name__)
@ -59,7 +59,7 @@ class PosController(http.Controller):
def a(self, debug=False, **k): def a(self, debug=False, **k):
if not request.session.uid: 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) js_list = manifest_list('js',db=request.db, debug=debug)
css_list = manifest_list('css',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: if uid == SUPERUSER_ID:
return super(mail_message, self)._search(cr, uid, args, offset=offset, limit=limit, order=order, 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_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] 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]: if group_user_id not in [group.id for group in group_ids]:
args = [('subtype_id', '!=', False)] + list(args) args = [('subtype_id', '!=', False)] + list(args)
return super(mail_message, self)._search(cr, uid, args, offset=offset, limit=limit, order=order, 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): def check_access_rule(self, cr, uid, ids, operation, context=None):
""" Add Access rules of mail.message for non-employee user: """ Add Access rules of mail.message for non-employee user:

View File

@ -6,12 +6,18 @@
z-index: 0; z-index: 0;
} }
.openerp .oe_form .oe_form_embedded_html.view_portal_payment_options {
overflow: visible;
}
.openerp .payment_acquirers { .openerp .payment_acquirers {
margin: -40px 0 -32px -24px;
position: relative; position: relative;
padding: 10px 15px; padding: 10px 15px;
right: -125px; /* improved margin according bootstrap3 */ right: -125px; /* improved margin according bootstrap3 */
width: 650px;
margin-left: 80px;
background: #729FCF; background: #729FCF;
background-image: -webkit-gradient(linear, left top, left bottom, from(#729FCF), to(#3465A4)); background-image: -webkit-gradient(linear, left top, left bottom, from(#729FCF), to(#3465A4));
background-image: -webkit-linear-gradient(top, #729FCF, #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: if this.state not in ('draft', 'cancel') and not this.invoiced:
result[this.id] = payment_acquirer.render_payment_block( result[this.id] = payment_acquirer.render_payment_block(
cr, uid, this.name, this.amount_total, this.pricelist_id.currency_id.id, 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 return result
def action_quotation_send(self, cr, uid, ids, context=None): 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: if this.type == 'out_invoice' and this.state not in ('draft', 'done') and not this.reconciled:
result[this.id] = payment_acquirer.render_payment_block( result[this.id] = payment_acquirer.render_payment_block(
cr, uid, this.number, this.residual, this.currency_id.id, 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 return result
def action_invoice_sent(self, cr, uid, ids, context=None): 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="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<notebook version="7.0" position="before"> <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> </notebook>
</field> </field>
</record> </record>
@ -19,7 +19,7 @@
<field name="inherit_id" ref="account.invoice_form"/> <field name="inherit_id" ref="account.invoice_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<notebook version="7.0" position="before"> <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> </notebook>
</field> </field>
</record> </record>

View File

@ -11,7 +11,7 @@
<div> <div>
<field name="group_payment_options" class="oe_inline"/> <field name="group_payment_options" class="oe_inline"/>
<label for="group_payment_options"/> <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"/> string="Configure payment acquiring methods" class="oe_link"/>
</div> </div>
</xpath> </xpath>

View File

@ -222,7 +222,7 @@
<field name="product_id" on_change="onchange_product_id(product_id)"/> <field name="product_id" on_change="onchange_product_id(product_id)"/>
</group> </group>
<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="product_uom" groups="product.group_uom"/>
<field name="location_id" groups="stock.group_locations"/> <field name="location_id" groups="stock.group_locations"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/> <field name="company_id" groups="base.group_multi_company" widget="selection"/>

View File

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

View File

@ -18,12 +18,18 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
############################################################################## ##############################################################################
from openerp import tools
import math
def rounding(f, r): 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: if not r:
return f return f
return round(f / r) * r return math.ceil(f / r) * r
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

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

View File

@ -22,7 +22,7 @@
import math import math
import re import re
from _common import rounding from _common import ceiling
from openerp import SUPERUSER_ID from openerp import SUPERUSER_ID
from openerp import tools from openerp import tools
@ -178,7 +178,7 @@ class product_uom(osv.osv):
return qty return qty
amount = qty / from_unit.factor amount = qty / from_unit.factor
if to_unit: if to_unit:
amount = rounding(amount * to_unit.factor, to_unit.rounding) amount = ceiling(amount * to_unit.factor, to_unit.rounding)
return amount return amount
def _compute_price(self, cr, uid, from_uom_id, price, to_uom_id=False): 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',)) _product_incoming_qty = _get_product_available_func(('confirmed','waiting','assigned'), ('in',))
def _product_lst_price(self, cr, uid, ids, name, arg, context=None): 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') product_uom_obj = self.pool.get('product.uom')
for id in ids:
# retrieve pricelist res.setdefault(id, 0.0)
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 product in self.browse(cr, uid, ids, context=context): for product in self.browse(cr, uid, ids, context=context):
if 'uom' in context: if 'uom' in context:
uom = product.uos_id or product.uom_id uom = product.uos_id or product.uom_id
@ -564,11 +559,7 @@ class product_product(osv.osv):
uom.id, product.list_price, context['uom']) uom.id, product.list_price, context['uom'])
else: else:
res[product.id] = product.list_price res[product.id] = product.list_price
res[product.id] = (res[product.id] or 0.0) * (product.price_margin or 1.0) + product.price_extra 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)
return res return res
def _save_product_lst_price(self, cr, uid, product_id, field_name, field_value, arg, context=None): def _save_product_lst_price(self, cr, uid, product_id, field_name, field_value, arg, context=None):

View File

@ -221,7 +221,6 @@
<kanban> <kanban>
<field name="color"/> <field name="color"/>
<field name="type"/> <field name="type"/>
<field name="image_small"/>
<field name="list_price"/> <field name="list_price"/>
<templates> <templates>
<t t-name="kanban-box"> <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') states = ('draft', 'open', 'paid')
sqlstr="""select 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) 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.list_price) as sale_expected,
sum(l.quantity * pt.standard_price) as normal_cost sum(l.quantity * pt.standard_price) as normal_cost
from account_invoice_line l from account_invoice_line l

View File

@ -48,8 +48,9 @@ class project_issue(osv.Model):
_mail_post_access = 'read' _mail_post_access = 'read'
_track = { _track = {
'stage_id': { 'stage_id': {
'project_issue.mt_issue_new': 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_stage': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence != 1, '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': { 'user_id': {
'project_issue.mt_issue_assigned': lambda self, cr, uid, obj, ctx=None: obj.user_id and obj.user_id.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): def _get_default_stage_id(self, cr, uid, context=None):
""" Gives default stage_id """ """ Gives default stage_id """
project_id = self._get_default_project_id(cr, uid, context=context) 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): def _resolve_project_id_from_context(self, cr, uid, context=None):
""" Returns ID of project based on the value of 'default_project_id' """ Returns ID of project based on the value of 'default_project_id'

View File

@ -200,7 +200,7 @@
<group> <group>
<field name="date_order"/> <field name="date_order"/>
<field name="origin" attrs="{'invisible': [('origin','=',False)]}"/> <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"/> <field name="company_id" groups="base.group_multi_company" widget="selection"/>
</group> </group>
</group> </group>

View File

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

View File

@ -27,7 +27,7 @@
<field name="inherit_id" ref="sale.view_order_form"/> <field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="user_id" position="after"> <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 name="categ_ids" widget="many2many_tags"/>
</field> </field>
</field> </field>

View File

@ -27,7 +27,7 @@
<field name="company_id" readonly="True"/> <field name="company_id" readonly="True"/>
</field> </field>
<field name="fiscal_position" position="after"> <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>
<field name="product_id" position="replace"> <field name="product_id" position="replace">
<field name="product_id" <field name="product_id"

View File

@ -106,15 +106,16 @@ class stock_fill_inventory(osv.osv_memory):
datas = {} datas = {}
res[location] = {} res[location] = {}
move_ids = move_obj.search(cr, uid, ['|',('location_dest_id','=',location),('location_id','=',location),('state','=','done')], context=context) 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): for move in move_obj.browse(cr, uid, move_ids, context=context):
lot_id = move.prodlot_id.id lot_id = move.prodlot_id.id
prod_id = move.product_id.id prod_id = move.product_id.id
if move.location_dest_id.id != move.location_id.id: if move.location_dest_id.id != move.location_id.id:
if move.location_dest_id.id == location: 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: 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)): 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"Image size excessive, uploaded images must be smaller "
u"than 42 million pixel") 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, 'name': upload.filename,
'datas': image_data.encode('base64'), 'datas': image_data.encode('base64'),
'datas_fname': upload.filename, 'datas_fname': upload.filename,
'res_model': 'ir.ui.view', 'res_model': 'ir.ui.view',
}, request.context) }, request.context)
url = website.urlplus('/website/image', { [attachment] = Attachments.read(
'model': 'ir.attachment', request.cr, request.uid, [attachment_id], ['website_url'],
'id': attachment_id, context=request.context)
'field': 'datas', url = attachment['website_url']
'max_height': MAX_IMAGE_HEIGHT,
'max_width': MAX_IMAGE_WIDTH,
})
except Exception, e: except Exception, e:
logger.exception("Failed to upload image to attachment") logger.exception("Failed to upload image to attachment")
message = unicode(e) message = unicode(e)

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import copy import copy
import re
import simplejson import simplejson
import werkzeug 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) 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): def save(self, cr, uid, res_id, value, xpath=None, context=None):
""" Update a view section. The view section may embed fields to write """ 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) arch = self.replace_arch_section(cr, uid, res_id, xpath, arch_section, context=context)
self.write(cr, uid, res_id, { self.write(cr, uid, res_id, {
'arch': etree.tostring(arch, encoding='utf-8').decode('utf-8') 'arch': self._pretty_arch(arch)
}, context=context) }, context=context)

View File

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

View File

@ -8,6 +8,7 @@ import urlparse
import werkzeug import werkzeug
import werkzeug.exceptions import werkzeug.exceptions
import werkzeug.utils
import werkzeug.wrappers import werkzeug.wrappers
# optional python-slugify import (https://github.com/un33k/python-slugify) # optional python-slugify import (https://github.com/un33k/python-slugify)
try: try:
@ -566,13 +567,40 @@ class ir_attachment(osv.osv):
'website_url': fields.function(_website_url_get, string="Attachment URL", type='char') '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): class res_partner(osv.osv):
_inherit = "res.partner" _inherit = "res.partner"
def google_map_img(self, cr, uid, ids, zoom=8, width=298, height=298, context=None): 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) partner = self.browse(cr, uid, ids[0], context=context)
params = { 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), 'size': "%sx%s" % (height, width),
'zoom': zoom, 'zoom': zoom,
'sensor': 'false', '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) setTimeout(function () { that.$element.trigger('slid') }, 0)
}) })
.emulateTransitionEnd(600) .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 { } else {
this.$element.trigger(e) this.$element.trigger(e)
if (e.isDefaultPrevented()) return if (e.isDefaultPrevented()) return

View File

@ -26,6 +26,7 @@
-webkit-box-shadow: none; -webkit-box-shadow: none;
-moz-box-shadow: none; -moz-box-shadow: none;
box-shadow: none; box-shadow: none;
-ms-filter: "alpha(opacity=50)";
} }
/* ---- OpenERP Style ---- {{{ */ /* ---- OpenERP Style ---- {{{ */
@ -250,6 +251,26 @@ ul.oe_menu_editor .disclose {
.existing-attachments .pager .disabled { .existing-attachments .pager .disabled {
display: none; 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 { .cke_widget_wrapper {
position: static !important; position: static !important;

View File

@ -23,6 +23,7 @@
background: transparent background: transparent
border: none border: none
+box-shadow(none) +box-shadow(none)
-ms-filter: "alpha(opacity=50)"
// }}} // }}}
@ -210,9 +211,27 @@ ul.oe_menu_editor
.font-icons-selected .font-icons-selected
background-color: #ddd background-color: #ddd
$attachment-border-color: #848490
.existing-attachments
.pager .disabled
display: none
.existing-attachments .pager .disabled .existing-attachment-cell
display: none 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. // wrapper positioned relatively for drag&drop widget which is disabled below.
// Breaks completely horribly crazy products listing page, so take it out. // Breaks completely horribly crazy products listing page, so take it out.

View File

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

View File

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

View File

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

View File

@ -324,21 +324,16 @@ div.carousel[data-snippet-id="slider"]
background-color: grey background-color: grey
.parallax .parallax
position: relative
background-size: cover 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 > div
display: table-cell position: relative
vertical-align: middle display: table
padding: 32px 0 width: 100%
min-height: 200px
> div
display: table-cell
vertical-align: middle
padding: 32px 0
/* Background */ /* Background */

View File

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

View File

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

View File

@ -19,13 +19,23 @@
edit: function () { edit: function () {
var self = this; var self = this;
$("body").off('click'); $("body").off('click');
website.snippet.stop_animation();
window.snippets = this.snippets = new website.snippet.BuildingBlock(this); window.snippets = this.snippets = new website.snippet.BuildingBlock(this);
this.snippets.appendTo(this.$el); this.snippets.appendTo(this.$el);
this.on('rte:ready', this, function () { this.on('rte:ready', this, function () {
self.snippets.$button.removeClass("hidden"); self.snippets.$button.removeClass("hidden");
website.snippet.stop_animation();
website.snippet.start_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); return this._super.apply(this, arguments);
@ -190,13 +200,14 @@
var mt = parseInt($target.css("margin-top") || 0); var mt = parseInt($target.css("margin-top") || 0);
var mb = parseInt($target.css("margin-bottom") || 0); var mb = parseInt($target.css("margin-bottom") || 0);
$el.css({ $el.css({
'position': 'absolute',
'width': $target.outerWidth(), 'width': $target.outerWidth(),
'height': $target.outerHeight() + mt + mb+1, 'top': pos.top - mt - 5,
'top': pos.top - mt,
'left': pos.left '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 () { show: function () {
this.$el.removeClass("hidden"); this.$el.removeClass("hidden");
@ -213,8 +224,8 @@
bind_snippet_click_editor: function () { bind_snippet_click_editor: function () {
var self = this; var self = this;
$(document).on('click', "#wrapwrap", function (event) { $("#wrapwrap").on('click', function (event) {
var $target = $(event.srcElement); var $target = $(event.srcElement || event.target);
if (!$target.attr("data-snippet-id")) { if (!$target.attr("data-snippet-id")) {
$target = $target.parents("[data-snippet-id]:first"); $target = $target.parents("[data-snippet-id]:first");
} }
@ -415,7 +426,6 @@
$target.data("overlay").remove(); $target.data("overlay").remove();
$target.removeData("overlay"); $target.removeData("overlay");
} }
self.create_overlay($target);
$target.find("[data-snippet-id]").each(function () { $target.find("[data-snippet-id]").each(function () {
var $snippet = $(this); var $snippet = $(this);
$snippet.removeData("snippet-editor"); $snippet.removeData("snippet-editor");
@ -423,10 +433,10 @@
$snippet.data("overlay").remove(); $snippet.data("overlay").remove();
$snippet.removeData("overlay"); $snippet.removeData("overlay");
} }
self.create_overlay($snippet);
}); });
// end // end
self.create_overlay($target);
self.make_active($target); self.make_active($target);
},0); },0);
} else { } else {
@ -571,6 +581,20 @@
var $target = $(this); var $target = $(this);
if (!$target.data('overlay')) { if (!$target.data('overlay')) {
var $zone = $(openerp.qweb.render('website.snippet_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.appendTo('#oe_manipulators');
$zone.data('target',$target); $zone.data('target',$target);
$target.data('overlay',$zone); $target.data('overlay',$zone);
@ -783,6 +807,7 @@
this.parent = parent; this.parent = parent;
this.$target = $(dom); this.$target = $(dom);
this.$overlay = this.$target.data('overlay'); this.$overlay = this.$target.data('overlay');
this.$overlay.find('a[data-toggle="dropdown"]').dropdown();
this.snippet_id = this.$target.data("snippet-id"); this.snippet_id = this.$target.data("snippet-id");
this._readXMLData(); this._readXMLData();
this.load_style_options(); this.load_style_options();
@ -1210,7 +1235,6 @@
on_clone: function () { on_clone: function () {
var $clone = this.$target.clone(false); var $clone = this.$target.clone(false);
var _class = $clone.attr("class").replace(/\s*(col-lg-offset-|col-md-offset-)([0-9-]+)/g, ''); var _class = $clone.attr("class").replace(/\s*(col-lg-offset-|col-md-offset-)([0-9-]+)/g, '');
_class += ' col-md-1';
$clone.attr("class", _class); $clone.attr("class", _class);
this.$target.after($clone); this.$target.after($clone);
this.hide_remove_button(); this.hide_remove_button();
@ -1258,6 +1282,11 @@
}); });
website.snippet.editorRegistry.slider = website.snippet.editorRegistry.resize.extend({ 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() { drop_and_build_snippet: function() {
var id = $(".carousel").length; var id = $(".carousel").length;
this.id = "myCarousel" + id; 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_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.$editor.find(".js_remove").on('click', function () {self.on_remove_slide(); return false;});
this.$target.carousel('pause');
this.rebind_event(); this.rebind_event();
}, },
on_add_slide: function () { on_add_slide: function () {
@ -1346,11 +1374,6 @@
}); });
website.snippet.editorRegistry.carousel = website.snippet.editorRegistry.slider.extend({ 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 () { clean_for_save: function () {
this._super(); this._super();
this.$target.css("background-image", ""); this.$target.css("background-image", "");
@ -1436,7 +1459,10 @@
self.$target.data("snippet-view").set_values(); self.$target.data("snippet-view").set_values();
}); });
this.$target.attr('contentEditable', 'false'); 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 () { scroll: function () {
var self = this; var self = this;

View File

@ -47,7 +47,7 @@
}, },
{ {
waitFor: '.oe_overlay_options .oe_options:visible', 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', placement: 'top',
title: _t("Customize banner's text"), title: _t("Customize banner's text"),
content: _t("Click in the text and start editing it. Click continue once it's done."), content: _t("Click in the text and start editing it. Click continue once it's done."),
@ -69,10 +69,17 @@
popover: { fixed: true }, 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', placement: 'bottom',
title: _t("Drag & Drop This Block"), 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 }, popover: { fixed: true },
}, },
{ {

View File

@ -140,8 +140,9 @@
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 class="modal-title">Select a Picture</h3> <h3 class="modal-title">Select a Picture</h3>
</div> </div>
<div class="modal-body"> <div class="modal-body has-error">
<div class="existing-attachments"/> <div class="existing-attachments"/>
<div class="help-block"/>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<a href="#" data-dismiss="modal" aria-hidden="true">Discard</a> <a href="#" data-dismiss="modal" aria-hidden="true">Discard</a>
@ -157,12 +158,27 @@
<li class="next disabled"><a href="#">Next →</a></li> <li class="next disabled"><a href="#">Next →</a></li>
</ul> </ul>
<div class="row mt16" t-foreach="rows" t-as="row"> <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"/> <img t-att-src="attachment.website_url" t-att-alt="attachment.name" class="img img-responsive"/>
</div> </div>
</div> </div>
</div> </div>
</t> </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"> <t t-name="website.editor.table.panel">
<table class="editorbar-panel"> <table class="editorbar-panel">
<tr t-foreach="rows"><td t-foreach="cols">&#8203;</td></tr> <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'> <div t-name="website.snippets.resize" data-snippet-id='resize'>
<!-- custom data for the widget --> <!-- custom data for the widget -->
<div class='oe_handles'> <div class='oe_handles'>
<div class='oe_handle n'></div> <div class='oe_handle n'><div></div></div>
<div class='oe_handle e'></div> <div class='oe_handle e'><div></div></div>
<div class='oe_handle w'></div> <div class='oe_handle w'><div></div></div>
<div class='oe_handle size'><div class="oe_handle_button size">Resize</div><div class="oe_border"/></div> <div class='oe_handle size'><div class="oe_handle_button size">Resize</div></div>
<div class='oe_handle s'></div> <div class='oe_handle s'><div></div></div>
</div> </div>
<div class='oe_snippet_thumbnail'>Margin resize</div> <div class='oe_snippet_thumbnail'>Margin resize</div>
</div> </div>

View File

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

View File

@ -792,7 +792,7 @@
</ul> </ul>
</li> </li>
<section class="oe_snippet_body parallax" <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"> data-scroll-background-ratio="0.3">
<div><div class="oe_structure"/></div> <div><div class="oe_structure"/></div>
</section> </section>
@ -804,10 +804,9 @@
<span class="oe_snippet_thumbnail_title">Parallax Slider</span> <span class="oe_snippet_thumbnail_title">Parallax Slider</span>
</div> </div>
<section class="oe_snippet_body parallax" data-snippet-id="parallax" <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"> data-scroll-background-ratio="0.3">
<div> <div><div><div class="oe_structure">
<div class="oe_structure">
<div id="myQuoteCarousel" class="carousel quotecarousel slide mb0" data-snippet-id="slider"> <div id="myQuoteCarousel" class="carousel quotecarousel slide mb0" data-snippet-id="slider">
<!-- Indicators --> <!-- Indicators -->
<ol class="carousel-indicators mb0"> <ol class="carousel-indicators mb0">
@ -854,8 +853,7 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div></div></div>
</div>
</section> </section>
</div> </div>
@ -888,7 +886,7 @@
<li class="dropdown-submenu"> <li class="dropdown-submenu">
<a tabindex="-1" href="#">Style</a> <a tabindex="-1" href="#">Style</a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li data-class="readable"><a>Readability</a></li> <li data-class="readable"><a>Narrow</a></li>
</ul> </ul>
</li> </li>
</div> </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/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 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'/> <t t-raw="head or ''" name='layout_head'/>
</head> </head>
@ -113,7 +113,7 @@
</b> </b>
</a> </a>
<ul class="dropdown-menu js_usermenu" role="menu"> <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 class="divider"/>
<li><a t-attf-href="/web/session/logout?redirect=/" role="menuitem">Logout</a></li> <li><a t-attf-href="/web/session/logout?redirect=/" role="menuitem">Logout</a></li>
</ul> </ul>

View File

@ -32,7 +32,7 @@
<div t-attf-class="form-group #{error and 'description' in error and 'has-error' or ''}"> <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> <label class="col-md-3 col-sm-4 control-label" for="description">Your Question</label>
<div class="col-md-7 col-sm-8"> <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> </div>
<div class="form-group"> <div class="form-group">

View File

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

View File

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

View File

@ -39,12 +39,12 @@
{ {
title: "Order Now", title: "Order Now",
waitFor: 'select[name="ticket-2"] option:contains(3):selected', 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", title: "Complete checkout",
waitFor: '#top_menu .my_cart_quantity:contains(5)', 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) { onload: function (tour) {
if ($("input[name='name']").val() === "") if ($("input[name='name']").val() === "")
$("input[name='name']").val("website_sale-test-shoptest"); $("input[name='name']").val("website_sale-test-shoptest");
@ -64,7 +64,7 @@
{ {
title: "Pay Now", title: "Pay Now",
waitFor: '#payment_method label:has(input:checked):has(img[title="transfer"])', 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", title: "finish",

View File

@ -5,6 +5,8 @@ def load_tests(loader, base, _):
{'redirect': '/page/website.homepage'})) {'redirect': '/page/website.homepage'}))
base.addTest(test_ui.WebsiteUiSuite(test_ui.full_path(__file__,'website_event_sale_test.js'), base.addTest(test_ui.WebsiteUiSuite(test_ui.full_path(__file__,'website_event_sale_test.js'),
{'redirect': '/page/website.homepage', 'user': 'demo', 'password': 'demo'})) {'redirect': '/page/website.homepage', 'user': 'demo', 'password': 'demo'}))
base.addTest(test_ui.WebsiteUiSuite(test_ui.full_path(__file__,'website_event_sale_test.js'), # Test has been commented in SAAS-3 ONLY, it must be activated in trunk.
{'path': '/', 'user': None})) # 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 return base

View File

@ -152,7 +152,7 @@
<t t-esc="speaker.name"/>, <t t-esc="speaker.name"/>,
</t> </t>
</li> </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"> <li class="text-muted fa fa-map-marker" t-if="track.location_id">
<span t-field="track.location_id"/> <span t-field="track.location_id"/>
</li> </li>
@ -230,7 +230,7 @@
</div> </div>
<div class="panel-body"> <div class="panel-body">
<b>Date</b><br/> <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/> <b>Duration</b><br/>
<span t-field="track.duration"/> minutes<br/> <span t-field="track.duration"/> minutes<br/>
<b>Location</b><br/> <b>Location</b><br/>

View File

@ -86,11 +86,14 @@ class website_hr_recruitment(http.Controller):
value = { value = {
'source_id' : imd.xmlid_to_res_id(cr, SUPERUSER_ID, 'hr_recruitment.source_website_company'), '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) value[f] = post.get(f)
for f in ['department_id', 'job_id']: for f in ['department_id', 'job_id']:
value[f] = int(post.get(f) or 0) 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) applicant_id = request.registry['hr.applicant'].create(cr, SUPERUSER_ID, value, context=context)
if post['ufile']: if post['ufile']:

View File

@ -51,14 +51,14 @@
<section class="oe_container"> <section class="oe_container">
<div class="oe_row"> <div class="oe_row">
<h2 class="oe_slogan">Post Your Jobs on Best Job Boards</h2> <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"> <div class="oe_span6">
<img class="oe_picture oe_screenshot" src="jobs3.png"> <img class="oe_picture oe_screenshot" src="jobs3.png">
</div> </div>
<div class="oe_span6"> <div class="oe_span6">
<p class='oe_mt32'> <p class='oe_mt32'>
Connect automatically to most famous job board websites; 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 email address automatically assigned to route applications
automatically to the right job position. automatically to the right job position.
</p><p> </p><p>

View File

@ -7,7 +7,7 @@ from openerp.addons.web.http import request
class WebsiteEmailDesigner(http.Controller): 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): def index(self, template, **kw):
values = { values = {
'template': template, 'template': template,
@ -15,6 +15,6 @@ class WebsiteEmailDesigner(http.Controller):
print template print template
return request.website.render("website_mail.designer_index", values) 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): def snippets(self):
return request.website._render('website_mail.email_designer_snippets') 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.osv import osv, fields
from openerp.tools.translate import _ from openerp.tools.translate import _
@ -39,52 +36,3 @@ class EmailTemplate(osv.Model):
help='Link to the website', 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) args = ['&', ('website_published', '=', True)] + list(args)
return super(MailMessage, self)._search(cr, uid, args, offset=offset, limit=limit, order=order, 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): def check_access_rule(self, cr, uid, ids, operation, context=None):
""" Add Access rules of mail.message for non-employee user: """ Add Access rules of mail.message for non-employee user:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,6 +7,8 @@ def load_tests(loader, base, _):
{'redirect': '/page/website.homepage'})) {'redirect': '/page/website.homepage'}))
base.addTest(test_ui.WebsiteUiSuite(test_ui.full_path(__file__,'website_sale-sale_process-test.js'), base.addTest(test_ui.WebsiteUiSuite(test_ui.full_path(__file__,'website_sale-sale_process-test.js'),
{'redirect': '/page/website.homepage', 'user': 'demo', 'password': 'demo'})) {'redirect': '/page/website.homepage', 'user': 'demo', 'password': 'demo'}))
base.addTest(test_ui.WebsiteUiSuite(test_ui.full_path(__file__,'website_sale-sale_process-test.js'), # Test has been commented in SAAS-3 ONLY, it must be activated in trunk.
{'path': '/', 'user': None})) # 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 return base

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