summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2015-03-25 19:14:46 +0100
committerChristophe Simonis <chs@odoo.com>2015-03-25 19:14:46 +0100
commit2565aa60e06f62e9689a2d139c7c9d9da3f3caa1 (patch)
tree24ba29ee26289539d1f206385c968f0a3a89ae8a /addons
parent2783f2e0a7727cbf2f67ec23cad83ef61fa45d6d (diff)
parentc2b476686083876df3c27a17885581592303c8f5 (diff)
[MERGE] forward port of branch saas-6 up to c2b4766
Diffstat (limited to 'addons')
-rw-r--r--addons/account/partner.py30
-rw-r--r--addons/account/res_config.py23
-rw-r--r--addons/account/tests/__init__.py1
-rw-r--r--addons/account/tests/test_fiscal_position.py41
-rw-r--r--addons/crm/base_partner_merge.py5
-rw-r--r--addons/crm/security/ir.model.access.csv6
-rw-r--r--addons/decimal_precision/decimal_precision.py5
-rw-r--r--addons/event/event_view.xml3
-rw-r--r--addons/hr/hr.py2
-rw-r--r--addons/hr_recruitment/hr_recruitment.py9
-rw-r--r--addons/hr_timesheet_sheet/hr_timesheet_sheet.py6
-rw-r--r--addons/im_chat/im_chat.py19
-rw-r--r--addons/im_livechat/controllers/livechat.py2
-rw-r--r--addons/im_livechat/im_livechat_data.xml20
-rw-r--r--addons/im_livechat/static/src/js/im_livechat.js1
-rw-r--r--addons/im_livechat/views/im_livechat.xml2
-rw-r--r--addons/l10n_be_invoice_bba/invoice.py11
-rw-r--r--addons/l10n_eu_service/wizard/wizard.py3
-rw-r--r--addons/mail/mail_thread.py13
-rw-r--r--addons/mrp/mrp.py10
-rw-r--r--addons/mrp/mrp_view.xml4
-rw-r--r--addons/product/pricelist.py9
-rw-r--r--addons/rating/static/src/js/rating.js16
-rw-r--r--addons/rating/views/rating_template.xml8
-rw-r--r--addons/rating/views/rating_view.xml21
-rw-r--r--addons/sale/wizard/sale_make_invoice_advance.py2
-rw-r--r--addons/sale_layout/views/report_invoice_layouted.xml2
-rw-r--r--addons/stock/stock.py4
-rw-r--r--addons/stock/wizard/stock_move.py20
-rw-r--r--addons/stock_account/stock_account.py17
-rw-r--r--addons/web/static/src/js/views/form_view.js10
-rw-r--r--addons/website/static/lib/summernote/src/js/core/range.js5
-rw-r--r--addons/website/static/src/js/summernote.js99
-rw-r--r--addons/website/static/src/js/website.snippets.editor.js10
-rw-r--r--addons/website/static/src/js/website.tour.rte.js2
-rw-r--r--addons/website_forum/static/src/js/website_forum.js11
-rw-r--r--addons/website_forum/static/src/js/website_forum.share.js2
-rw-r--r--addons/website_forum/views/website_forum.xml10
-rw-r--r--addons/website_hr_recruitment/models/hr_job.py2
-rw-r--r--addons/website_mail/views/snippets.xml2
-rw-r--r--addons/website_mail/views/theme/airmail_snippets.xml2
-rw-r--r--addons/website_mail/views/theme/cleave_snippets.xml2
-rw-r--r--addons/website_mail/views/theme/go_snippets.xml2
-rw-r--r--addons/website_mail/views/theme/narrative_snippets.xml2
-rw-r--r--addons/website_mail/views/theme/neopolitan_snippets.xml2
-rw-r--r--addons/website_mail/views/theme/skyline_snippets.xml2
-rw-r--r--addons/website_mail/views/theme/sunday_snippets.xml2
-rw-r--r--addons/website_mail/views/theme/zenflat_snippets.xml2
-rw-r--r--addons/website_sale/views/templates.xml2
-rw-r--r--addons/website_sale_options/controllers/main.py9
-rw-r--r--addons/website_sale_options/static/src/js/website_sale.js1
51 files changed, 367 insertions, 129 deletions
diff --git a/addons/account/partner.py b/addons/account/partner.py
index e5ff919243a..2b9cb47bb63 100644
--- a/addons/account/partner.py
+++ b/addons/account/partner.py
@@ -123,22 +123,24 @@ class account_fiscal_position(osv.osv):
else:
delivery = partner
- domain = [
- ('auto_apply', '=', True),
- '|', ('vat_required', '=', False), ('vat_required', '=', partner.vat_subjected),
- ]
- if delivery.country_id.id:
- fiscal_position_ids = self.search(cr, uid, domain + [('country_id', '=', delivery.country_id.id)], context=context, limit=1)
+ domains = [[('auto_apply', '=', True), ('vat_required', '=', partner.vat_subjected)]]
+ if partner.vat_subjected:
+ # Possibly allow fallback to non-VAT positions, if no VAT-required position matches
+ domains += [[('auto_apply', '=', True), ('vat_required', '=', False)]]
+
+ for domain in domains:
+ if delivery.country_id.id:
+ fiscal_position_ids = self.search(cr, uid, domain + [('country_id', '=', delivery.country_id.id)], context=context, limit=1)
+ if fiscal_position_ids:
+ return fiscal_position_ids[0]
+
+ fiscal_position_ids = self.search(cr, uid, domain + [('country_group_id.country_ids', '=', delivery.country_id.id)], context=context, limit=1)
+ if fiscal_position_ids:
+ return fiscal_position_ids[0]
+
+ fiscal_position_ids = self.search(cr, uid, domain + [('country_id', '=', None), ('country_group_id', '=', None)], context=context, limit=1)
if fiscal_position_ids:
return fiscal_position_ids[0]
-
- fiscal_position_ids = self.search(cr, uid, domain + [('country_group_id.country_ids', '=', delivery.country_id.id)], context=context, limit=1)
- if fiscal_position_ids:
- return fiscal_position_ids[0]
-
- fiscal_position_ids = self.search(cr, uid, domain + [('country_id', '=', None), ('country_group_id', '=', None)], context=context, limit=1)
- if fiscal_position_ids:
- return fiscal_position_ids[0]
return False
class account_fiscal_position_tax(osv.osv):
diff --git a/addons/account/res_config.py b/addons/account/res_config.py
index 9e994f735aa..0cf32abab40 100644
--- a/addons/account/res_config.py
+++ b/addons/account/res_config.py
@@ -135,16 +135,33 @@ class account_config_settings(osv.osv_memory):
'company_id', 'income_currency_exchange_account_id',
type='many2one',
relation='account.account',
- string="Gain Exchange Rate Account",
- domain="[('type', '=', 'other')]"),
+ string="Gain Exchange Rate Account",
+ domain="[('type', '=', 'other'), ('company_id', '=', company_id)]]"),
'expense_currency_exchange_account_id': fields.related(
'company_id', 'expense_currency_exchange_account_id',
type="many2one",
relation='account.account',
string="Loss Exchange Rate Account",
- domain="[('type', '=', 'other')]"),
+ domain="[('type', '=', 'other'), ('company_id', '=', company_id)]]"),
}
+ def _check_account_gain(self, cr, uid, ids, context=None):
+ for obj in self.browse(cr, uid, ids, context=context):
+ if obj.income_currency_exchange_account_id.company_id and obj.company_id != obj.income_currency_exchange_account_id.company_id:
+ return False
+ return True
+
+ def _check_account_loss(self, cr, uid, ids, context=None):
+ for obj in self.browse(cr, uid, ids, context=context):
+ if obj.expense_currency_exchange_account_id.company_id and obj.company_id != obj.expense_currency_exchange_account_id.company_id:
+ return False
+ return True
+
+ _constraints = [
+ (_check_account_gain, 'The company of the gain exchange rate account must be the same than the company selected.', ['income_currency_exchange_account_id']),
+ (_check_account_loss, 'The company of the loss exchange rate account must be the same than the company selected.', ['expense_currency_exchange_account_id']),
+ ]
+
def _default_company(self, cr, uid, context=None):
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
return user.company_id.id
diff --git a/addons/account/tests/__init__.py b/addons/account/tests/__init__.py
index 152f89cdcc1..75f03b9a8be 100644
--- a/addons/account/tests/__init__.py
+++ b/addons/account/tests/__init__.py
@@ -2,3 +2,4 @@ from . import test_tax
from . import test_search
from . import test_reconciliation
from . import test_account_move_closed_period
+from . import test_fiscal_position
diff --git a/addons/account/tests/test_fiscal_position.py b/addons/account/tests/test_fiscal_position.py
new file mode 100644
index 00000000000..046b558860d
--- /dev/null
+++ b/addons/account/tests/test_fiscal_position.py
@@ -0,0 +1,41 @@
+from openerp.tests.common import TransactionCase
+
+class TestFiscalPosition(TransactionCase):
+ """Tests for fiscal positions in auto apply (account.fiscal.position).
+ If a partner has a vat number, the fiscal positions with "vat_required=True"
+ are prefered.
+ """
+
+ def setUp(self):
+ super(TestFiscalPosition, self).setUp()
+ self.fiscal_position_model = self.registry('account.fiscal.position')
+ self.res_partner_model = self.registry('res.partner')
+ self.res_country_model = self.registry('res.country')
+
+ def test_fiscal_position(self):
+ cr, uid = self.cr, self.uid
+ company_id = 1
+ country_id = self.res_country_model.search(cr, uid, [('name', '=', 'France')])[0]
+ partner_id = self.res_partner_model.create(cr, uid, dict(
+ name="George",
+ vat_subjected=True,
+ notify_email="always",
+ country_id=country_id))
+ fp_b2c_id = self.fiscal_position_model.create(cr, uid, dict(name="EU-VAT-FR-B2C",
+ auto_apply=True,
+ country_id=country_id,
+ vat_required=False,
+ sequence=1))
+ fp_b2b_id = self.fiscal_position_model.create(cr, uid, dict(name="EU-VAT-FR-B2B",
+ auto_apply=True,
+ country_id=country_id,
+ vat_required=True,
+ sequence=2))
+ res = self.fiscal_position_model.get_fiscal_position(cr, uid, company_id, partner_id)
+ self.assertEquals(fp_b2b_id, res,
+ "Fiscal position detection should pick B2B position as 1rst match")
+
+ self.fiscal_position_model.write(cr, uid, [fp_b2b_id], {'auto_apply': False})
+ res = self.fiscal_position_model.get_fiscal_position(cr, uid, company_id, partner_id)
+ self.assertEquals(fp_b2c_id, res,
+ "Fiscal position detection should pick B2C position as 1rst match")
diff --git a/addons/crm/base_partner_merge.py b/addons/crm/base_partner_merge.py
index 321c5caa517..7f958080ec1 100644
--- a/addons/crm/base_partner_merge.py
+++ b/addons/crm/base_partner_merge.py
@@ -9,6 +9,7 @@ import operator
import psycopg2
import re
from ast import literal_eval
+from openerp.exceptions import ValidationError
from openerp.tools import mute_logger
# Validation Library https://pypi.python.org/pypi/validate_email/1.1
@@ -246,12 +247,12 @@ class MergePartnerAutomatic(osv.TransientModel):
for record in proxy.browse(cr, openerp.SUPERUSER_ID, record_ids, context=context):
try:
proxy_model = self.pool[record.model]
- field_type = proxy_model._columns[record.name].__class__._type
+ column = proxy_model._columns[record.name]
except KeyError:
# unknown model or field => skip
continue
- if field_type == 'function':
+ if isinstance(column, fields.function):
continue
for partner in src_partners:
diff --git a/addons/crm/security/ir.model.access.csv b/addons/crm/security/ir.model.access.csv
index 09150132f14..5030138ae9a 100644
--- a/addons/crm/security/ir.model.access.csv
+++ b/addons/crm/security/ir.model.access.csv
@@ -23,6 +23,6 @@ access_crm_lead_tag_manager,crm_lead_tag manager,model_crm_lead_tag,base.group_s
access_crm_phonecall_category,crm_phonecall_category,model_crm_phonecall_category,base.group_user,1,0,0,0
access_crm_phonecall_category_salesman,crm_phonecall_category salesman,model_crm_phonecall_category,base.group_sale_salesman,1,1,1,0
access_crm_phonecall_category_manager,crm_phonecall_category manager,model_crm_phonecall_category,base.group_sale_manager,1,1,1,1
-access_crm_lost_reason,crm.lost.reason,model_crm_lost_reason,base.group_sale_manager,1,1,1,1
-access_crm_lost_reason,crm.lost.reason,model_crm_lost_reason,base.group_sale_salesman,1,1,1,0
-access_crm_lost_reason,crm.lost.reason,model_crm_lost_reason,base.group_user,1,0,0,0 \ No newline at end of file
+access_crm_lost_reason_manager,crm.lost.reason.manager,model_crm_lost_reason,base.group_sale_manager,1,1,1,1
+access_crm_lost_reason_salesman,crm.lost.reason.salesman,model_crm_lost_reason,base.group_sale_salesman,1,1,1,0
+access_crm_lost_reason_user,crm.lost.reason.user,model_crm_lost_reason,base.group_user,1,0,0,0
diff --git a/addons/decimal_precision/decimal_precision.py b/addons/decimal_precision/decimal_precision.py
index dee5dd0fe28..a33d76be9e6 100644
--- a/addons/decimal_precision/decimal_precision.py
+++ b/addons/decimal_precision/decimal_precision.py
@@ -48,11 +48,6 @@ class decimal_precision(orm.Model):
def clear_cache(self, cr):
"""clear cache and update models. Notify other workers to restart their registry."""
self.precision_get.clear_cache(self)
- env = openerp.api.Environment(cr, SUPERUSER_ID, {})
- for model in self.pool.values():
- for field in model._fields.values():
- if field.type == 'float':
- field._setup_digits(env)
RegistryManager.signal_registry_change(cr.dbname)
def create(self, cr, uid, data, context=None):
diff --git a/addons/event/event_view.xml b/addons/event/event_view.xml
index 981e7b4db9b..eddcabf9b6b 100644
--- a/addons/event/event_view.xml
+++ b/addons/event/event_view.xml
@@ -287,6 +287,9 @@
<field name="event_id" />
<field name="state"/>
<field name="message_unread" invisible="1"/>
+ <button name="confirm_registration" string="Confirm Registration" states="draft" type="object" icon="gtk-apply"/>
+ <button name="button_reg_close" string="Attended the Event" states="open" type="object" icon="gtk-jump-to"/>
+ <button name="button_reg_cancel" string="Cancel Registration" states="draft,open" type="object" icon="gtk-cancel"/>
</tree>
</field>
</record>
diff --git a/addons/hr/hr.py b/addons/hr/hr.py
index 2e8c33f312c..94a2bd79dc8 100644
--- a/addons/hr/hr.py
+++ b/addons/hr/hr.py
@@ -86,7 +86,7 @@ class hr_job(osv.Model):
_description = "Job Position"
_inherit = ['mail.thread']
_columns = {
- 'name': fields.char('Job Name', required=True, select=True),
+ 'name': fields.char('Job Name', required=True, select=True, translate=True),
'expected_employees': fields.function(_get_nbr_employees, string='Total Forecasted Employees',
help='Expected number of employees for this job position after new recruitment.',
store = {
diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py
index de389c468ac..26c8a2a47e4 100644
--- a/addons/hr_recruitment/hr_recruitment.py
+++ b/addons/hr_recruitment/hr_recruitment.py
@@ -528,12 +528,13 @@ class hr_applicant(osv.Model):
'post': True,
'notify': True,
}, context=compose_ctx)
+ values = self.pool['mail.compose.message'].onchange_template_id(
+ cr, uid, [compose_id], stage.template_id.id, 'mass_mail', self._name, False, context=compose_ctx)['value']
+ if values.get('attachment_ids'):
+ values['attachment_ids'] = [(6, 0, values['attachment_ids'])]
self.pool['mail.compose.message'].write(
cr, uid, [compose_id],
- self.pool['mail.compose.message'].onchange_template_id(
- cr, uid, [compose_id],
- stage.template_id.id, 'mass_mail', self._name, False,
- context=compose_ctx)['value'],
+ values,
context=compose_ctx)
self.pool['mail.compose.message'].send_mail(cr, uid, [compose_id], context=compose_ctx)
return res
diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py
index ed59585da6d..953cb1942aa 100644
--- a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py
+++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py
@@ -584,7 +584,7 @@ class hr_timesheet_sheet_sheet_day(osv.osv):
((
select
min(hrt.id) as id,
- 'UTC' as timezone,
+ p.tz as timezone,
l.date::date as name,
s.id as sheet_id,
sum(l.unit_amount) as total_timesheet,
@@ -594,6 +594,10 @@ class hr_timesheet_sheet_sheet_day(osv.osv):
hr_analytic_timesheet hrt
JOIN account_analytic_line l ON l.id = hrt.line_id
LEFT JOIN hr_timesheet_sheet_sheet s ON s.id = hrt.sheet_id
+ JOIN hr_employee e ON s.employee_id = e.id
+ JOIN resource_resource r ON e.resource_id = r.id
+ LEFT JOIN res_users u ON r.user_id = u.id
+ LEFT JOIN res_partner p ON u.partner_id = p.id
group by l.date::date, s.id, timezone
) union (
select
diff --git a/addons/im_chat/im_chat.py b/addons/im_chat/im_chat.py
index 689432e4157..07292c8b7f9 100644
--- a/addons/im_chat/im_chat.py
+++ b/addons/im_chat/im_chat.py
@@ -296,13 +296,26 @@ class im_chat_shortcode(osv.Model):
'description' : fields.char('Description'),
}
+ def _sanitize_shorcode(self, substitution):
+ """ Sanitize the shortcode substitution :
+ - HTML substitution : only allow the img tag (smiley)
+ - escape other substitutions to avoid XSS
+ """
+ is_img_tag = re.match(r'''^<img\s+src=('|")([^'"]*)\1\s*/?>$''', substitution, re.M|re.I)
+ if is_img_tag:
+ return substitution
+ return cgi.escape(substitution)
+
def create(self, cr, uid, values, context=None):
if values.get('substitution'):
- values['substitution'] = tools.html_sanitize(values['substitution']).strip()
- if not values.get('substitution'):
- raise UserError(_("Substitution contains only forbidden characters. Please fill it with something else."))
+ values['substitution'] = self._sanitize_shorcode(values['substitution'])
return super(im_chat_shortcode, self).create(cr, uid, values, context=context)
+ def write(self, cr, uid, ids, values, context=None):
+ if values.get('substitution'):
+ values['substitution'] = self._sanitize_shorcode(values['substitution'])
+ return super(im_chat_shortcode, self).write(cr, uid, ids, values, context=context)
+
def replace_shortcode(self, cr, uid, message, context=None):
ids = self.search(cr, uid, [], context=context)
for shortcode in self.browse(cr, uid, ids, context=context):
diff --git a/addons/im_livechat/controllers/livechat.py b/addons/im_livechat/controllers/livechat.py
index c3015d33f87..47129d8116a 100644
--- a/addons/im_livechat/controllers/livechat.py
+++ b/addons/im_livechat/controllers/livechat.py
@@ -27,7 +27,7 @@ class LivechatController(http.Controller):
info["username"] = kwargs.get("username", "Visitor")
# find the country from the request
country_id = False
- country_code = request.session.geoip and request.session.geoip.get('country_name') or False
+ country_code = request.session.geoip and request.session.geoip.get('country_code') or False
if country_code:
country_ids = registry.get('res.country').search(cr, uid, [('code', '=', country_code)], context=context)
if country_ids:
diff --git a/addons/im_livechat/im_livechat_data.xml b/addons/im_livechat/im_livechat_data.xml
index 8394d25c16d..7a97ae788e1 100644
--- a/addons/im_livechat/im_livechat_data.xml
+++ b/addons/im_livechat/im_livechat_data.xml
@@ -56,5 +56,25 @@
</record>
+ <!-- Smiley for rating to be displayed in the conversation -->
+ <record id="im_livechat_rating_0" model="im_chat.shortcode">
+ <field name="source">:rating_0</field>
+ <field name="substitution">&lt;img src='/rating/static/src/img/rating_0.png'/&gt;</field>
+ <field name="description">Rating Smiley Bad</field>
+ </record>
+
+ <record id="im_livechat_rating_5" model="im_chat.shortcode">
+ <field name="source">:rating_5</field>
+ <field name="substitution">&lt;img src='/rating/static/src/img/rating_5.png'/&gt;</field>
+ <field name="description">Rating Smiley Okay</field>
+ </record>
+
+ <record id="im_livechat_rating_10" model="im_chat.shortcode">
+ <field name="source">:rating_10</field>
+ <field name="substitution">&lt;img src='/rating/static/src/img/rating_10.png'/&gt;</field>
+ <field name="description">Rating Smiley Good</field>
+ </record>
+
+
</data>
</openerp> \ No newline at end of file
diff --git a/addons/im_livechat/static/src/js/im_livechat.js b/addons/im_livechat/static/src/js/im_livechat.js
index 9d27e2b7969..7c0fb4f675f 100644
--- a/addons/im_livechat/static/src/js/im_livechat.js
+++ b/addons/im_livechat/static/src/js/im_livechat.js
@@ -260,6 +260,7 @@ var Feedback = Widget.extend({
return user_session.rpc("/rating/livechat/feedback", {uuid: uuid, rate: this.rating, reason : this.reason}).then(function(res) {
if(close){
self.trigger("feedback_sent"); // will close the conversation
+ self.conversation.send_message(_.str.sprintf(_t("I rated you with :rating_%d"), self.rating), "message");
}
});
}
diff --git a/addons/im_livechat/views/im_livechat.xml b/addons/im_livechat/views/im_livechat.xml
index b13fe40772b..bfd38b3dc91 100644
--- a/addons/im_livechat/views/im_livechat.xml
+++ b/addons/im_livechat/views/im_livechat.xml
@@ -145,6 +145,7 @@
<!-- the js code to initialize the LiveSupport object -->
<template id="loader" name="Javascript initializing the LiveSupport">
+ <t t-translate="off">
$(function() {
odoo.define('im_livechat.livesupport', function (require) {
var im_livechat = require('im_livechat.im_livechat');
@@ -164,6 +165,7 @@
});
odoo.init();
});
+ </t>
</template>
</data>
diff --git a/addons/l10n_be_invoice_bba/invoice.py b/addons/l10n_be_invoice_bba/invoice.py
index 00a276431ec..f83bb98572c 100644
--- a/addons/l10n_be_invoice_bba/invoice.py
+++ b/addons/l10n_be_invoice_bba/invoice.py
@@ -189,13 +189,10 @@ class account_invoice(osv.osv):
reference_type = vals['reference_type']
else:
reference_type = inv.reference_type or ''
- if reference_type == 'bba':
- if vals.has_key('reference'):
- bbacomm = vals['reference']
- else:
- bbacomm = inv.reference or ''
- if self.check_bbacomm(bbacomm):
- reference = re.sub('\D', '', bbacomm)
+
+ if reference_type == 'bba' and 'reference' in vals:
+ if self.check_bbacomm(vals['reference']):
+ reference = re.sub('\D', '', vals['reference'])
vals['reference'] = '+++' + reference[0:3] + '/' + reference[3:7] + '/' + reference[7:] + '+++'
same_ids = self.search(cr, uid,
[('id', '!=', inv.id), ('type', '=', 'out_invoice'),
diff --git a/addons/l10n_eu_service/wizard/wizard.py b/addons/l10n_eu_service/wizard/wizard.py
index ac672c16899..dba6fb1b007 100644
--- a/addons/l10n_eu_service/wizard/wizard.py
+++ b/addons/l10n_eu_service/wizard/wizard.py
@@ -151,7 +151,8 @@ class l10n_eu_service(models.TransientModel):
'ref_tax_code_id': tx_code.id,
'ref_base_sign': -1,
'ref_tax_sign': -1,
- 'description': "EU-VAT-%s-S" % country.code
+ 'description': "EU-VAT-%s-S" % country.code,
+ 'sequence': 1000,
}
tax = account_tax.create(data_tax)
if self.fiscal_position_id:
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
index e2058fabdf2..e77750fc6dd 100644
--- a/addons/mail/mail_thread.py
+++ b/addons/mail/mail_thread.py
@@ -1820,7 +1820,17 @@ class mail_thread(osv.AbstractModel):
('model', '=', self._name),
('res_id', '=', record_id)], limit=1, context=context)
if msg_ids:
- self.pool.get('mail.notification')._notify(cr, uid, msg_ids[0], partners_to_notify=partner_ids, context=context)
+ notification_obj = self.pool.get('mail.notification')
+ notification_obj._notify(cr, uid, msg_ids[0], partners_to_notify=partner_ids, context=context)
+ message = message_obj.browse(cr, uid, msg_ids[0], context=context)
+ if message.parent_id:
+ partner_ids_to_parent_notify = set(partner_ids).difference(partner.id for partner in message.parent_id.notified_partner_ids)
+ for partner_id in partner_ids_to_parent_notify:
+ notification_obj.create(cr, uid, {
+ 'message_id': message.parent_id.id,
+ 'partner_id': partner_id,
+ 'is_read': True,
+ }, context=context)
def message_auto_subscribe(self, cr, uid, ids, updated_fields, context=None, values=None):
""" Handle auto subscription. Two methods for auto subscription exist:
@@ -1902,6 +1912,7 @@ class mail_thread(osv.AbstractModel):
self._message_auto_subscribe_notify(cr, uid, ids, user_pids, context=context)
+
return True
#------------------------------------------------------
diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py
index 413f131ac25..4e19284e8c4 100644
--- a/addons/mrp/mrp.py
+++ b/addons/mrp/mrp.py
@@ -99,6 +99,16 @@ class mrp_workcenter(osv.osv):
value = {'costs_hour': cost.standard_price}
return {'value': value}
+ def _check_capacity_per_cycle(self, cr, uid, ids, context=None):
+ for obj in self.browse(cr, uid, ids, context=context):
+ if obj.capacity_per_cycle <= 0.0:
+ return False
+ return True
+
+ _constraints = [
+ (_check_capacity_per_cycle, 'The capacity per cycle must be strictly positive.', ['capacity_per_cycle']),
+ ]
+
class mrp_routing(osv.osv):
"""
For specifying the routings of Work Centers.
diff --git a/addons/mrp/mrp_view.xml b/addons/mrp/mrp_view.xml
index 017fc7a6502..f39fe4caf2d 100644
--- a/addons/mrp/mrp_view.xml
+++ b/addons/mrp/mrp_view.xml
@@ -417,10 +417,10 @@
<field name="product_efficiency"/>
</group>
</group>
- <lable string="Properties"/>
+ <separator string="Properties" groups="product.group_mrp_properties"/>
<group>
<group>
- <field name="property_ids" widget="many2many_tags"/>
+ <field name="property_ids" widget="many2many_tags" groups="product.group_mrp_properties"/>
</group>
<group/>
</group>
diff --git a/addons/product/pricelist.py b/addons/product/pricelist.py
index 320d5438a7d..01c6f247f66 100644
--- a/addons/product/pricelist.py
+++ b/addons/product/pricelist.py
@@ -381,7 +381,7 @@ class product_pricelist_version(osv.osv):
'active': fields.boolean('Active',
help="When a version is duplicated it is set to non active, so that the " \
"dates do not overlaps with original version. You should change the dates " \
- "and reactivate the pricelist", copy=False),
+ "and reactivate the pricelist"),
'items_id': fields.one2many('product.pricelist.item',
'price_version_id', 'Price List Items', required=True, copy=True),
'date_start': fields.date('Start Date', help="First valid date for the version."),
@@ -420,6 +420,13 @@ class product_pricelist_version(osv.osv):
['date_start', 'date_end'])
]
+ def copy(self, cr, uid, id, default=None, context=None):
+ # set active False to prevent overlapping active pricelist
+ # versions
+ if not default:
+ default = {}
+ default['active'] = False
+ return super(product_pricelist_version, self).copy(cr, uid, id, default, context=context)
class product_pricelist_item(osv.osv):
def _price_field_get(self, cr, uid, context=None):
diff --git a/addons/rating/static/src/js/rating.js b/addons/rating/static/src/js/rating.js
new file mode 100644
index 00000000000..87f16cd1a50
--- /dev/null
+++ b/addons/rating/static/src/js/rating.js
@@ -0,0 +1,16 @@
+odoo.define('rating.kanban', function(require) {
+"use strict";
+
+var common = require('web_kanban.common');
+
+common.KanbanRecord.include({
+ on_card_clicked: function() {
+ if (this.view.dataset.model === 'rating.rating') {
+ this.$('a').first().click();
+ } else {
+ this._super.apply(this, arguments);
+ }
+ },
+});
+
+});
diff --git a/addons/rating/views/rating_template.xml b/addons/rating/views/rating_template.xml
index 773c1565e0d..7ff1c3b80e2 100644
--- a/addons/rating/views/rating_template.xml
+++ b/addons/rating/views/rating_template.xml
@@ -1,6 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data>
+ <!-- backend assets -->
+ <template id="assets_backend" name="rating assets" inherit_id="web.assets_backend">
+ <xpath expr="." position="inside">
+ <script type="text/javascript" src="/rating/static/src/js/rating.js"></script>
+ </xpath>
+ </template>
+
+ <!-- Thanks page -->
<template id="rating_external_page_view">
<head>
<link rel='stylesheet' href='/web/static/lib/bootstrap/css/bootstrap.css'/>
diff --git a/addons/rating/views/rating_view.xml b/addons/rating/views/rating_view.xml
index 332d0495ab7..ceb33af8b10 100644
--- a/addons/rating/views/rating_view.xml
+++ b/addons/rating/views/rating_view.xml
@@ -7,6 +7,8 @@
<field name="arch" type="xml">
<kanban create="false" class="oe_background_grey">
<field name="rating"/>
+ <field name="res_id"/>
+ <field name="res_model"/>
<field name="res_name"/>
<field name="create_date"/>
<field name="partner_id"/>
@@ -14,15 +16,16 @@
<field name="feedback"/>
<templates>
<t t-name="kanban-box">
- <div style="min-height:100px;width:100px">
- <div t-attf-title="Name : #{ record.res_name.raw_value } &lt;br&gt;
- #{ record.partner_id.value ? 'Customer : ' + record.partner_id.value + '&lt;br&gt;' : '' }
- #{ record.rated_partner_id.value ? 'Responsible : ' + record.rated_partner_id.value+'&lt;br&gt;' : '' }
- Date : #{ moment(record.create_date.raw_value).format('L') }
- #{ record.feedback.raw_value ? '&lt;br&gt;'+'Reason : ' + record.feedback.raw_value : '' }"
- data-html="true" data-placement="bottom">
- <img t-attf-src="rating/static/src/img/rating_#{record.rating.raw_value}.png" class="oe_kanban_image"/>
- </div>
+ <div class="oe_kanban_card oe_kanban_global_click">
+ <a t-att-href="_.str.sprintf('/web?#id=%s&amp;view_type=form&amp;model=%s', record.res_id.raw_value, record.res_model.raw_value)">
+ <img t-attf-src="rating/static/src/img/rating_#{record.rating.raw_value}.png"
+ t-attf-title="Name : #{ record.res_name.raw_value } &lt;br&gt;
+ #{ record.partner_id.value ? 'Customer : ' + record.partner_id.value + '&lt;br&gt;' : '' }
+ #{ record.rated_partner_id.value ? 'Responsible : ' + record.rated_partner_id.value+'&lt;br&gt;' : '' }
+ Date : #{ moment(record.create_date.raw_value).format('L') }
+ #{ record.feedback.raw_value ? '&lt;br&gt;'+'Reason : ' + record.feedback.raw_value : '' }"
+ data-html="true" data-placement="bottom"/>
+ </a>
</div>
</t>
</templates>
diff --git a/addons/sale/wizard/sale_make_invoice_advance.py b/addons/sale/wizard/sale_make_invoice_advance.py
index 47ab3d5bb05..28c48bc8a67 100644
--- a/addons/sale/wizard/sale_make_invoice_advance.py
+++ b/addons/sale/wizard/sale_make_invoice_advance.py
@@ -101,7 +101,7 @@ class sale_advance_payment_inv(osv.osv_memory):
if wizard.amount <= 0.00:
raise UserError(_('The value of Advance Amount must be positive.'))
if wizard.advance_payment_method == 'percentage':
- inv_amount = sale.amount_total * wizard.amount / 100
+ inv_amount = sale.amount_untaxed * wizard.amount / 100
if not res.get('name'):
res['name'] = self._translate_advance(cr, uid, percentage=True, context=dict(context, lang=sale.partner_id.lang)) % (wizard.amount)
else:
diff --git a/addons/sale_layout/views/report_invoice_layouted.xml b/addons/sale_layout/views/report_invoice_layouted.xml
index 3b247260e55..e3d34bfe1bb 100644
--- a/addons/sale_layout/views/report_invoice_layouted.xml
+++ b/addons/sale_layout/views/report_invoice_layouted.xml
@@ -18,7 +18,7 @@
<td class="text-right">
<span t-field="l.price_unit"/>
</td>
- <td groups="sale.group_discount_per_so_line"><span t-field="l.discount"/></td>
+ <td class="text-right" groups="sale.group_discount_per_so_line"><span t-field="l.discount"/></td>
<td>
<span t-esc="', '.join(map(lambda x: x.name, l.invoice_line_tax_id))"/>
</td>
diff --git a/addons/stock/stock.py b/addons/stock/stock.py
index 1a90116ebc5..84d8615af7c 100644
--- a/addons/stock/stock.py
+++ b/addons/stock/stock.py
@@ -309,7 +309,7 @@ class stock_quant(osv.osv):
'create_date': fields.datetime('Creation Date', readonly=True),
'in_date': fields.datetime('Incoming Date', readonly=True, select=True),
- 'history_ids': fields.many2many('stock.move', 'stock_quant_move_rel', 'quant_id', 'move_id', 'Moves', help='Moves that operate(d) on this quant'),
+ 'history_ids': fields.many2many('stock.move', 'stock_quant_move_rel', 'quant_id', 'move_id', 'Moves', help='Moves that operate(d) on this quant', copy=False),
'company_id': fields.many2one('res.company', 'Company', help="The company to which the quants belong", required=True, readonly=True, select=True),
'inventory_value': fields.function(_calc_inventory_value, string="Inventory Value", type='float', readonly=True),
@@ -1805,7 +1805,7 @@ class stock_move(osv.osv):
# used for colors in tree views:
'scrapped': fields.related('location_dest_id', 'scrap_location', type='boolean', relation='stock.location', string='Scrapped', readonly=True),
- 'quant_ids': fields.many2many('stock.quant', 'stock_quant_move_rel', 'move_id', 'quant_id', 'Moved Quants'),
+ 'quant_ids': fields.many2many('stock.quant', 'stock_quant_move_rel', 'move_id', 'quant_id', 'Moved Quants', copy=False),
'reserved_quant_ids': fields.one2many('stock.quant', 'reservation_id', 'Reserved quants'),
'linked_move_operation_ids': fields.one2many('stock.move.operation.link', 'move_id', string='Linked Operations', readonly=True, help='Operations that impact this move for the computation of the remaining quantities'),
'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Quantity', digits=0,
diff --git a/addons/stock/wizard/stock_move.py b/addons/stock/wizard/stock_move.py
index a6b241a6a98..376e7084e70 100644
--- a/addons/stock/wizard/stock_move.py
+++ b/addons/stock/wizard/stock_move.py
@@ -88,13 +88,13 @@ class stock_move_scrap(osv.osv_memory):
context=context)
if context.get('active_id'):
move = self.pool.get('stock.move').browse(cr, uid, context['active_id'], context=context)
- return {
- 'view_type': 'form',
- 'view_mode': 'form',
- 'res_model': 'stock.picking',
- 'type': 'ir.actions.act_window',
- 'res_id': move.picking_id.id,
- 'context': context
- }
- else:
- return {'type': 'ir.actions.act_window_close'}
+ if move.picking_id:
+ return {
+ 'view_type': 'form',
+ 'view_mode': 'form',
+ 'res_model': 'stock.picking',
+ 'type': 'ir.actions.act_window',
+ 'res_id': move.picking_id.id,
+ 'context': context
+ }
+ return {'type': 'ir.actions.act_window_close'}
diff --git a/addons/stock_account/stock_account.py b/addons/stock_account/stock_account.py
index 2fcc661c079..7ff3f3001b8 100644
--- a/addons/stock_account/stock_account.py
+++ b/addons/stock_account/stock_account.py
@@ -79,13 +79,22 @@ class stock_quant(osv.osv):
'''
if context is None:
context = {}
+ account_period = self.pool['account.period']
super(stock_quant, self)._price_update(cr, uid, quant_ids, newprice, context=context)
- ctx = context.copy()
for quant in self.browse(cr, uid, quant_ids, context=context):
move = self._get_latest_move(cr, uid, quant, context=context)
- # this is where we post accounting entries for adjustment
- ctx['force_valuation_amount'] = newprice - quant.cost
- self._account_entry_move(cr, uid, [quant], move, context=ctx)
+ valuation_update = newprice - quant.cost
+ # this is where we post accounting entries for adjustment, if needed
+ if not quant.company_id.currency_id.is_zero(valuation_update):
+ # adjustment journal entry needed, cost has been updated
+ period_id = (context.get('force_period') or
+ account_period.find(cr, uid, move.date, context=context)[0])
+ period = account_period.browse(cr, uid, period_id, context=context)
+ # If neg quant period already closed (likely with manual valuation), skip update
+ if period.state != 'done':
+ ctx = dict(context, force_valuation_amount=valuation_update)
+ self._account_entry_move(cr, uid, [quant], move, context=ctx)
+
#update the standard price of the product, only if we would have done it if we'd have had enough stock at first, which means
#1) the product cost's method is 'real'
#2) we just fixed a negative quant caused by an outgoing shipment
diff --git a/addons/web/static/src/js/views/form_view.js b/addons/web/static/src/js/views/form_view.js
index 6c97c076781..9c4b9add4f1 100644
--- a/addons/web/static/src/js/views/form_view.js
+++ b/addons/web/static/src/js/views/form_view.js
@@ -845,6 +845,16 @@ var FormView = View.extend(common.FieldManagerMixin, {
}
}
}
+ // Heuristic to assign a proper sequence number for new records that
+ // are added in a dataset containing other lines with existing sequence numbers
+ if (!self.datarecord.id && self.fields.sequence &&
+ !_.has(values, 'sequence') && !_.isEmpty(self.dataset.cache)) {
+ // Find current max or min sequence (editable top/bottom)
+ var current = _[prepend_on_create ? "min" : "max"](
+ _.map(self.dataset.cache, function(o){return o.values.sequence})
+ );
+ values['sequence'] = prepend_on_create ? current - 1 : current + 1;
+ }
if (form_invalid) {
self.set({'display_invalid_fields': true});
first_invalid_field.focus();
diff --git a/addons/website/static/lib/summernote/src/js/core/range.js b/addons/website/static/lib/summernote/src/js/core/range.js
index ea37337f27c..aa72cd31e55 100644
--- a/addons/website/static/lib/summernote/src/js/core/range.js
+++ b/addons/website/static/lib/summernote/src/js/core/range.js
@@ -474,6 +474,11 @@ define([
return this.normalize();
}
+ // if apply the editor to a P, LI...
+ if (dom.isText(sc) && /^P|^LI|^H[1-7]/.test(sc.parentNode.nodeName.toUpperCase())) {
+ return this.normalize();
+ }
+
// find inline top ancestor
var topAncestor;
if (dom.isInline(sc)) {
diff --git a/addons/website/static/src/js/summernote.js b/addons/website/static/src/js/summernote.js
index 7fd6f7fbfe4..579a18f9f38 100644
--- a/addons/website/static/src/js/summernote.js
+++ b/addons/website/static/src/js/summernote.js
@@ -502,7 +502,7 @@ dom.removeBetween = function (sc, so, ec, eo, towrite) {
if (sc.tagName) {
sc = sc.childNodes[so] || dom.firstChild(ec);
so = 0;
- if (!dom.hasContentBefore(sc)) {
+ if (!dom.hasContentBefore(sc) && towrite) {
sc.parentNode.insertBefore(document.createTextNode('\u00A0'), sc);
}
}
@@ -557,18 +557,19 @@ dom.removeBetween = function (sc, so, ec, eo, towrite) {
sc = before ? dom.lastChild(before) : dom.firstChild(after);
so = sc.textContent.length;
- if (before) {
- var text = sc.textContent.replace(/[ \t\n\r]+$/, '\u00A0');
- so -= sc.textContent.length - text.length;
- sc.textContent = text;
- }
if (towrite && !node.firstChild && node.parentNode && !dom.isNotBreakable(node)) {
- var br = $("<br/>")[0];
+ sc = $("<br/>")[0];
+ so = 0;
node.appendChild(br);
- sc = br;
+ } else if (!ancestor.children.length && !ancestor.textContent.match(/\S|\u00A0/)) {
+ sc = $("<br/>")[0];
so = 0;
+ $(ancestor).prepend(sc);
+ } else if (before) {
+ var text = sc.textContent.replace(/[ \t\n\r]+$/, '\u00A0');
+ so -= sc.textContent.length - text.length;
+ sc.textContent = text;
}
- dom.autoMerge(sc, false);
} else {
@@ -704,7 +705,7 @@ dom.isFont = function (node) {
(nodeName === "SPAN" && (
node.className.match(/(^|\s)fa(\s|$)/i) ||
node.className.match(/(^|\s)(text|bg)-/i) ||
- (node.attributes.style && node.attributes.style.value.match(/(^|\s)(color|background-color):/i)))) );
+ (node.attributes.style && node.attributes.style.value.match(/(^|\s)(color|background-color|font-size):/i)))) );
};
dom.isVisibleText = function (textNode) {
return !!textNode.textContent.match(/\S|\u00A0/);
@@ -1024,7 +1025,7 @@ $.summernote.pluginEvents.enter = function (event, editor, layoutInfo) {
var node = dom.node(r.sc);
var last = node;
- while (node && dom.isSplitable(node) && !dom.isList(node)) {
+ while (node && dom.isSplitable(node) && !dom.isList(node) && !dom.isEditable(node)) {
last = node;
node = node.parentNode;
}
@@ -1158,8 +1159,6 @@ $.summernote.pluginEvents.delete = function (event, editor, layoutInfo) {
}
r = r.deleteContents();
r.select();
- event.preventDefault();
- return false;
}
var target = r.ec;
@@ -1284,8 +1283,6 @@ $.summernote.pluginEvents.backspace = function (event, editor, layoutInfo) {
}
r = r.deleteContents();
r.select();
- event.preventDefault();
- return false;
}
var target = r.sc;
@@ -1729,12 +1726,19 @@ $.summernote.pluginEvents.formatBlock = function (event, editor, layoutInfo, sTa
$.summernote.pluginEvents.removeFormat = function (event, editor, layoutInfo, value) {
var $editable = layoutInfo.editable();
$editable.data('NoteHistory').recordUndo($editable);
+
var node = range.create().sc.parentNode;
document.execCommand('removeFormat');
- document.execCommand('removeFormat');
+ document.execCommand('removeFormat'); // 2 times because some browser don't clean all in one time
var r = range.create();
r = dom.merge(node, r.sc, r.so, r.ec, r.eo, null, true);
- range.create(r.sc, r.so, r.ec, r.eo).select();
+ r = range.create(r.sc, r.so, r.ec, r.eo);
+ r.select();
+
+ $.summernote.pluginEvents.justify(event, editor, layoutInfo, "");
+
+ r.clean().select();
+
event.preventDefault();
return false;
};
@@ -1766,6 +1770,9 @@ eventHandler.toolbar.button.updateRecentColor = function (elBtn, sEvent, sValue)
options.fontSizes = [_t('Default'), 8, 9, 10, 11, 12, 14, 18, 24, 36, 48, 62];
$.summernote.pluginEvents.applyFont = function (event, editor, layoutInfo, color, bgcolor, size) {
+ var $editable = layoutInfo.editable();
+ $editable.data('NoteHistory').recordUndo($editable);
+
var rng = range.create();
var startPoint = rng.getStartPoint();
var endPoint = rng.getEndPoint();
@@ -1955,7 +1962,7 @@ $.summernote.pluginEvents.applyFont = function (event, editor, layoutInfo, color
if (i>0 && (font = dom.ancestor(nodes[i-1], dom.isFont))) {
className2 = font.getAttribute('class');
style2 = font.getAttribute('style');
- if (node !== font && className == className2 && style == style2) {
+ if (node !== font && className == className2 && style == style2 && (node.parentNode === font.parentNode || node.parentNode === font)) {
remove(node, font);
nodes.splice(i,1);
i--;
@@ -1968,6 +1975,8 @@ $.summernote.pluginEvents.applyFont = function (event, editor, layoutInfo, color
};
$.summernote.pluginEvents.fontSize = function (event, editor, layoutInfo, value) {
var $editable = layoutInfo.editable();
+ $editable.data('NoteHistory').recordUndo($editable);
+
event.preventDefault();
$.summernote.pluginEvents.applyFont(event, editor, layoutInfo, null, null, value);
editor.afterCommand($editable);
@@ -2001,6 +2010,60 @@ $.summernote.pluginEvents.backColor = function (event, editor, layoutInfo, backC
}
$.summernote.pluginEvents.applyFont(event, editor, layoutInfo, null, backColor, null);
editor.afterCommand($editable);
+ };
+
+ $.summernote.pluginEvents.justify = function (event, editor, layoutInfo, align) {
+ var rng = range.create();
+ var startPoint = rng.getStartPoint();
+ var endPoint = rng.getEndPoint();
+
+ // get list of nodes to change
+ var nodes = [];
+ nodes.push(dom.ancestor(startPoint.node, dom.isPara) || dom.ancestor(startPoint.node, dom.isCell));
+ nodes.push(dom.ancestor(endPoint.node, dom.isPara) || dom.ancestor(endPoint.node, dom.isCell));
+ dom.walkPoint(startPoint, endPoint, function (point) {
+ var node = point.node;
+ if (dom.isPara(node) || dom.isCell(node)) {
+ nodes.push(point.node);
+ }
+ });
+ nodes = list.unique(nodes);
+
+ $(nodes).not(":has(div,p,li,h1,h2,h3,h4,h5,h6,h7,td,th)").each(function () {
+ var $node = $(this).css('text-align', '');
+ var css = window.getComputedStyle(this);
+ var textAlign = css.textAlign;
+ if (textAlign === "start") {
+ textAlign = css.direction === "ltr" ? "left" : "right";
+ }
+ if(textAlign !== align) {
+ $node.css('text-align', align);
+ }
+ }).find("*").css('text-align', "");
+ };
+ $.summernote.pluginEvents.justifyLeft = function (event, editor, layoutInfo) {
+ var $editable = layoutInfo.editable();
+ $editable.data('NoteHistory').recordUndo($editable);
+ $.summernote.pluginEvents.justify(event, editor, layoutInfo, 'left');
+ editor.afterCommand($editable);
+ };
+ $.summernote.pluginEvents.justifyCenter = function (event, editor, layoutInfo) {
+ var $editable = layoutInfo.editable();
+ $editable.data('NoteHistory').recordUndo($editable);
+ $.summernote.pluginEvents.justify(event, editor, layoutInfo, 'center');
+ editor.afterCommand($editable);
+ };
+ $.summernote.pluginEvents.justifyRight = function (event, editor, layoutInfo) {
+ var $editable = layoutInfo.editable();
+ $editable.data('NoteHistory').recordUndo($editable);
+ $.summernote.pluginEvents.justify(event, editor, layoutInfo, 'right');
+ editor.afterCommand($editable);
+ };
+ $.summernote.pluginEvents.justifyFull = function (event, editor, layoutInfo) {
+ var $editable = layoutInfo.editable();
+ $editable.data('NoteHistory').recordUndo($editable);
+ $.summernote.pluginEvents.justify(event, editor, layoutInfo, 'justify');
+ editor.afterCommand($editable);
};
//////////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/addons/website/static/src/js/website.snippets.editor.js b/addons/website/static/src/js/website.snippets.editor.js
index f0d0b1c03ef..fc0b4be2e7d 100644
--- a/addons/website/static/src/js/website.snippets.editor.js
+++ b/addons/website/static/src/js/website.snippets.editor.js
@@ -213,16 +213,6 @@ var BuildingBlock = Widget.extend({
throw new Error("Wrong snippets xml definition");
}
- $ul.children().tooltip({
- delay: { "show": 500, "hide": 100 },
- container: 'body',
- title: function () {
- return (navigator.appVersion.indexOf('Mac') > -1 ? 'CMD' : 'CTRL')+'+SHIFT+'+($(this).index()+1);
- },
- trigger: 'hover',
- placement: 'top'
- }).on('click', function () {$(this).tooltip('hide');});
-
// t-snippet
$html.find('[data-oe-type="snippet"][data-oe-name]').each(function () {
$('<div/>').insertAfter(this).append(this).attr('name', $(this).data('oe-name'));
diff --git a/addons/website/static/src/js/website.tour.rte.js b/addons/website/static/src/js/website.tour.rte.js
index 6a50dc444c2..b068c11f604 100644
--- a/addons/website/static/src/js/website.tour.rte.js
+++ b/addons/website/static/src/js/website.tour.rte.js
@@ -140,7 +140,7 @@ Tour.register({
},
{
waitFor: '#wrapwrap > main > div > section .row > div:first:has( font:last:containsExact(Bat) )',
- element: '.note-air-popover button[data-event="undo"]',
+ element: '.note-image-popover button[data-event="undo"]',
title: "undo",
},
{
diff --git a/addons/website_forum/static/src/js/website_forum.js b/addons/website_forum/static/src/js/website_forum.js
index c3abbd19a2e..5d166c099bc 100644
--- a/addons/website_forum/static/src/js/website_forum.js
+++ b/addons/website_forum/static/src/js/website_forum.js
@@ -143,8 +143,7 @@ website.if_dom_contains('.website_forum', function () {
return true;
});
-
- $('.link_url').on('change', function (ev) {
+ $('.link_url, .o_forum_post_link').on('change', function (ev) { // keep .link_url for compat
ev.preventDefault();
var $link = $(ev.currentTarget);
var display_error = function(){
@@ -153,7 +152,7 @@ website.if_dom_contains('.website_forum', function () {
'Please enter valid URL. Example: http://www.odoo.com'+
'</div>');
$link.parent().append($warning);
- $("button#btn_post_your_article")[0].disabled = true;
+ $link.parents('form').find('button')[0].disabled = true;
};
var url = $link.val();
@@ -163,10 +162,10 @@ website.if_dom_contains('.website_forum', function () {
var regex = new RegExp("(http(s)?://.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)");
if(regex.test(url)){
ajax.jsonRpc("/forum/get_url_title", 'call', {'url': url}).then(function (data) {
- if(data){
+ if (data) {
$("input[name='post_name']")[0].value = data;
- $('button#btn_post_your_article').prop('disabled', false);
- }else{
+ $link.parents('form').find('button')[0].disabled = false;
+ } else {
display_error();
}
diff --git a/addons/website_forum/static/src/js/website_forum.share.js b/addons/website_forum/static/src/js/website_forum.share.js
index 710642a2949..3ad1f3d0e7d 100644
--- a/addons/website_forum/static/src/js/website_forum.share.js
+++ b/addons/website_forum/static/src/js/website_forum.share.js
@@ -41,7 +41,7 @@ var ForumShare = SocialShare.extend({
website.ready().done(function() {
// Store social share data to display modal on next page
- $(document.body).on('click', '.oe_social_share_call', function() {
+ $(document.body).on('click', ':not(.karma_required).oe_social_share_call', function() {
var social_data = {};
social_data['target_type'] = $(this).data('social-target-type');
sessionStorage.setItem('social_share', JSON.stringify(social_data));
diff --git a/addons/website_forum/views/website_forum.xml b/addons/website_forum/views/website_forum.xml
index b76c34cc5da..eacb24c134d 100644
--- a/addons/website_forum/views/website_forum.xml
+++ b/addons/website_forum/views/website_forum.xml
@@ -435,7 +435,7 @@
<label class="col-sm-2 control-label" for="content_link">URL to Share</label>
<div class="col-sm-8">
<input type="text" name="content_link" required="True" t-attf-value="#{post_name}"
- class="form-control mb16 link_url" placeholder="e.g. https://www.odoo.com"/>
+ class="form-control mb16 o_forum_post_link" placeholder="e.g. https://www.odoo.com"/>
</div>
</div>
<div class="form-group">
@@ -454,8 +454,9 @@
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-8">
- <button t-attf-class="btn btn-primary #{forum.allow_share and 'oe_social_share_call' or ''}" disabled="True" id="btn_post_your_article"
- data-hashtags="#link" data-social-target-type="other">Post</button>
+ <button t-attf-class="btn btn-primary #{forum.allow_share and 'oe_social_share_call' or ''} #{(user.karma &lt; forum.karma_ask) and 'karma_required' or ''}"
+ t-att-data-karma="forum.karma_ask"
+ disabled="True" data-hashtags="#link" data-social-target-type="other">Post</button>
</div>
</div>
</form>
@@ -481,7 +482,8 @@
<input type="hidden" name="karma_retag" t-attf-value="#{forum.karma_retag}" id="karma_retag"/>
<input type="text" name="post_tags" placeholder="Tags" class="form-control js_select2"/>
<br/><br/>
- <button t-attf-class="btn btn-primary #{forum.allow_share and 'oe_social_share_call' or ''}"
+ <button t-attf-class="btn btn-primary #{forum.allow_share and 'oe_social_share_call' or ''} #{(user.karma &lt; forum.karma_ask) and 'karma_required' or ''}"
+ t-att-data-karma="forum.karma_ask"
data-hashtags="#discussion" data-social-target-type="default">Post Your Topic</button>
</form>
</t>
diff --git a/addons/website_hr_recruitment/models/hr_job.py b/addons/website_hr_recruitment/models/hr_job.py
index f7a6ad5ddc8..c73a2f352fb 100644
--- a/addons/website_hr_recruitment/models/hr_job.py
+++ b/addons/website_hr_recruitment/models/hr_job.py
@@ -18,5 +18,5 @@ class hr_job(osv.osv):
return super(hr_job, self).job_open(cr, uid, ids, context)
_columns = {
- 'website_description': fields.html('Website description'),
+ 'website_description': fields.html('Website description', translate=True),
}
diff --git a/addons/website_mail/views/snippets.xml b/addons/website_mail/views/snippets.xml
index fa2da8ecf88..1c75f87243d 100644
--- a/addons/website_mail/views/snippets.xml
+++ b/addons/website_mail/views/snippets.xml
@@ -342,7 +342,7 @@
<div class="snippet_row bg-color" style="padding:0;width:600px;max-width:600px;margin:0 auto;background: #fff repeat top /100%;color:#777777">
<table style="width:100%;text-align:justify;margin:0 auto;background:inherit;color:inherit;border-collapse:collapse;color:inherit">
<tr>
- <td style="padding:10px 0;font-size:14px">
+ <td style="padding:10px 30px;font-size:14px">
<p style="margin:0">Dear John</p>
<p style="margin:0">Great stories have personality. Consider telling a great story that provides personality. Writing a story with personality for potential clients will assist with making a relationship connection. This shows up in small quirks like word choices or phrases. Write from your point of view, not from someone else's experience.<br/></p>
<p style="margin:0">Great stories are for everyone even when only written for just one person. If you try to write with a wide general audience in mind, your story will ring false and be bland. No one will be interested. Write for one person. If it’s genuine for the one, it’s genuine for the rest.</p>
diff --git a/addons/website_mail/views/theme/airmail_snippets.xml b/addons/website_mail/views/theme/airmail_snippets.xml
index 125a3ba730d..eb8da5183f7 100644
--- a/addons/website_mail/views/theme/airmail_snippets.xml
+++ b/addons/website_mail/views/theme/airmail_snippets.xml
@@ -481,7 +481,7 @@
<div class="snippet_row bg-color" style="padding:0;width:600px;max-width:600px;margin:0 auto;background:#fff repeat top /100%;color:#464646">
<table style="width:100%;text-align:justify;margin:0 auto;border-collapse:collapse;color:inherit">
<tr>
- <td style="padding:10px 0;font-size:14px">
+ <td style="padding:10px 30px;font-size:14px">
<p style="margin:0">Dear John</p>
<p style="margin:0">Great stories have personality. Consider telling a great story that provides personality. Writing a story with personality for potential clients will assist with making a relationship connection. This shows up in small quirks like word choices or phrases. Write from your point of view, not from someone else's experience.<br/></p>
<p style="margin:0">Great stories are for everyone even when only written for just one person. If you try to write with a wide general audience in mind, your story will ring false and be bland. No one will be interested. Write for one person. If it’s genuine for the one, it’s genuine for the rest.</p>
diff --git a/addons/website_mail/views/theme/cleave_snippets.xml b/addons/website_mail/views/theme/cleave_snippets.xml
index 559abc2273d..18e92bbc805 100644
--- a/addons/website_mail/views/theme/cleave_snippets.xml
+++ b/addons/website_mail/views/theme/cleave_snippets.xml
@@ -465,7 +465,7 @@
<div class="snippet_row bg-color" style="padding:0;width:600px;max-width:600px;margin:0 auto;background: #fff repeat top /100%;color:#89898d">
<table style="width:100%;text-align:justify;margin:0 auto;border-collapse:collapse;color:inherit">
<tr>
- <td style="padding:10px 0;font-size:14px">
+ <td style="padding:10px 30px;font-size:14px">
<p style="margin:0">Dear John</p>
<p style="margin:0">Great stories have personality. Consider telling a great story that provides personality. Writing a story with personality for potential clients will assist with making a relationship connection. This shows up in small quirks like word choices or phrases. Write from your point of view, not from someone else's experience.<br/></p>
<p style="margin:0">Great stories are for everyone even when only written for just one person. If you try to write with a wide general audience in mind, your story will ring false and be bland. No one will be interested. Write for one person. If it’s genuine for the one, it’s genuine for the rest.</p>
diff --git a/addons/website_mail/views/theme/go_snippets.xml b/addons/website_mail/views/theme/go_snippets.xml
index c39bff04efb..7ae20bd258e 100644
--- a/addons/website_mail/views/theme/go_snippets.xml
+++ b/addons/website_mail/views/theme/go_snippets.xml
@@ -510,7 +510,7 @@
<div class="snippet_row bg-color" style="padding:0;width:600px;max-width:600px;margin:0 auto;background: #EAEAEA repeat top /100%;color:#282D29">
<table style="width:100%;text-align:justify;margin:0 auto;border-collapse:collapse;color:inherit">
<tr>
- <td style="padding:10px 0;font-size:14px">
+ <td style="padding:10px 30px;font-size:14px">
<p style="margin:0">Dear John</p>
<p style="margin:0">Great stories have personality. Consider telling a great story that provides personality. Writing a story with personality for potential clients will assist with making a relationship connection. This shows up in small quirks like word choices or phrases. Write from your point of view, not from someone else's experience.<br/></p>
<p style="margin:0">Great stories are for everyone even when only written for just one person. If you try to write with a wide general audience in mind, your story will ring false and be bland. No one will be interested. Write for one person. If it’s genuine for the one, it’s genuine for the rest.</p>
diff --git a/addons/website_mail/views/theme/narrative_snippets.xml b/addons/website_mail/views/theme/narrative_snippets.xml
index 988bd2560ff..987b09c5fb6 100644
--- a/addons/website_mail/views/theme/narrative_snippets.xml
+++ b/addons/website_mail/views/theme/narrative_snippets.xml
@@ -482,7 +482,7 @@
<div class="snippet_row bg-color" style="padding:0;width:600px;max-width:600px;margin:0 auto;background: #FFFFFF repeat top /100%;color:#37302d">
<table style="width:100%;text-align:justify;margin:0 auto;border-collapse:collapse;color:inherit">
<tr>
- <td style="padding:10px 0;font-size:14px">
+ <td style="padding:10px 30px;font-size:14px">
<p style="margin:0">Dear John</p>
<p style="margin:0">Great stories have personality. Consider telling a great story that provides personality. Writing a story with personality for potential clients will assist with making a relationship connection. This shows up in small quirks like word choices or phrases. Write from your point of view, not from someone else's experience.<br/></p>
<p style="margin:0">Great stories are for everyone even when only written for just one person. If you try to write with a wide general audience in mind, your story will ring false and be bland. No one will be interested. Write for one person. If it’s genuine for the one, it’s genuine for the rest.</p>
diff --git a/addons/website_mail/views/theme/neopolitan_snippets.xml b/addons/website_mail/views/theme/neopolitan_snippets.xml
index 0f9bc78487a..98c4c557851 100644
--- a/addons/website_mail/views/theme/neopolitan_snippets.xml
+++ b/addons/website_mail/views/theme/neopolitan_snippets.xml
@@ -449,7 +449,7 @@
<div class="snippet_row bg-color" style="padding:0;width:600px;max-width:600px;margin:0 auto;background: #fff repeat top /100%;color:#168484">
<table style="width:100%;text-align:justify;margin:0 auto;inherit;border-collapse:collapse;color:inherit">
<tr>
- <td style="padding:10px 0;font-size:14px;line-height:20px">
+ <td style="padding:10px 30px;font-size:14px;line-height:20px">
<p style="margin:0">Dear John</p>
<p style="margin:0">Great stories have personality. Consider telling a great story that provides personality. Writing a story with personality for potential clients will assist with making a relationship connection. This shows up in small quirks like word choices or phrases. Write from your point of view, not from someone else's experience.<br/></p>
<p style="margin:0">Great stories are for everyone even when only written for just one person. If you try to write with a wide general audience in mind, your story will ring false and be bland. No one will be interested. Write for one person. If it’s genuine for the one, it’s genuine for the rest.</p>
diff --git a/addons/website_mail/views/theme/skyline_snippets.xml b/addons/website_mail/views/theme/skyline_snippets.xml
index 1add15e0a4a..126b57e18dd 100644
--- a/addons/website_mail/views/theme/skyline_snippets.xml
+++ b/addons/website_mail/views/theme/skyline_snippets.xml
@@ -464,7 +464,7 @@
<div class="snippet_row bg-color" style="padding:0;width:600px;max-width:600px;margin:0 auto;background: #fff repeat top /100%;color:#37302d">
<table style="width:100%;text-align:justify;margin:0 auto;border-collapse:collapse;color:inherit">
<tr>
- <td style="padding:10px 0;font-size:14px;line-height:20px">
+ <td style="padding:10px 30px;font-size:14px;line-height:20px">
<p style="margin:0">Dear John</p>
<p style="margin:0">Great stories have personality. Consider telling a great story that provides personality. Writing a story with personality for potential clients will assist with making a relationship connection. This shows up in small quirks like word choices or phrases. Write from your point of view, not from someone else's experience.<br/></p>
<p style="margin:0">Great stories are for everyone even when only written for just one person. If you try to write with a wide general audience in mind, your story will ring false and be bland. No one will be interested. Write for one person. If it’s genuine for the one, it’s genuine for the rest.</p>
diff --git a/addons/website_mail/views/theme/sunday_snippets.xml b/addons/website_mail/views/theme/sunday_snippets.xml
index a0dcba83133..aa678507e9d 100644
--- a/addons/website_mail/views/theme/sunday_snippets.xml
+++ b/addons/website_mail/views/theme/sunday_snippets.xml
@@ -465,7 +465,7 @@
<div class="snippet_row bg-color" style="padding:0;width:600px;max-width:600px;margin:0 auto;background: #fff repeat top /100%;color:#414141">
<table style="width:100%;text-align:justify;margin:0 auto;color:inherit;border-collapse:collapse">
<tr>
- <td style="padding:10px 0;font-size:14px">
+ <td style="padding:10px 30px;font-size:14px">
<p style="margin:0">Dear John</p>
<p style="margin:0">Great stories have personality. Consider telling a great story that provides personality. Writing a story with personality for potential clients will assist with making a relationship connection. This shows up in small quirks like word choices or phrases. Write from your point of view, not from someone else's experience.<br/></p>
<p style="margin:0">Great stories are for everyone even when only written for just one person. If you try to write with a wide general audience in mind, your story will ring false and be bland. No one will be interested. Write for one person. If it’s genuine for the one, it’s genuine for the rest.</p>
diff --git a/addons/website_mail/views/theme/zenflat_snippets.xml b/addons/website_mail/views/theme/zenflat_snippets.xml
index a76461a4f3f..75574bf2ff3 100644
--- a/addons/website_mail/views/theme/zenflat_snippets.xml
+++ b/addons/website_mail/views/theme/zenflat_snippets.xml
@@ -485,7 +485,7 @@
<div class="snippet_row bg-color" style="padding:0;width:600px;max-width:600px;margin:0 auto;background: #fff repeat top /100%;color:#414141">
<table style="width:100%;text-align:justify;margin:0 auto;color:#414141;border-collapse:collapse">
<tr>
- <td style="padding:10px 0;font-size:14px">
+ <td style="padding:10px 30px;font-size:14px">
<p style="margin:0">Dear John</p>
<p style="margin:0">Great stories have personality. Consider telling a great story that provides personality. Writing a story with personality for potential clients will assist with making a relationship connection. This shows up in small quirks like word choices or phrases. Write from your point of view, not from someone else's experience.<br/></p>
<p style="margin:0">Great stories are for everyone even when only written for just one person. If you try to write with a wide general audience in mind, your story will ring false and be bland. No one will be interested. Write for one person. If it’s genuine for the one, it’s genuine for the rest.</p>
diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml
index 1de33347990..b252111722b 100644
--- a/addons/website_sale/views/templates.xml
+++ b/addons/website_sale/views/templates.xml
@@ -440,7 +440,7 @@
<ul class="list-unstyled">
<t t-set="inc" t-value="0"/>
<t t-foreach="variant_id.value_ids" t-as="value_id">
- <li t-if="value_id.product_ids" class="form-group js_attribute_value" style="margin: 0;">
+ <li class="form-group js_attribute_value" style="margin: 0;">
<label class="control-label" style="margin: 0 20px;">
<input type="radio" class="js_variant_change" t-att-checked="'checked' if not inc else ''" t-att-name="'attribute-%s-%s' % (product.id, variant_id.attribute_id.id)" t-att-value="value_id.id" style="vertical-align: top; margin-right: 10px;"/>
<span t-field="value_id.name"/>
diff --git a/addons/website_sale_options/controllers/main.py b/addons/website_sale_options/controllers/main.py
index f2b53d84fb9..95e981fe77c 100644
--- a/addons/website_sale_options/controllers/main.py
+++ b/addons/website_sale_options/controllers/main.py
@@ -23,8 +23,11 @@ class website_sale_options(website_sale):
return r
@http.route(['/shop/cart/update_option'], type='http', auth="public", methods=['POST'], website=True)
- def cart_options_update_json(self, product_id, add_qty=1, set_qty=0, goto_shop=None, **kw):
+ def cart_options_update_json(self, product_id, add_qty=1, set_qty=0, goto_shop=None, lang=None, **kw):
cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
+ if lang:
+ context = dict(context, lang=lang)
+ request.website = request.website.with_context(context)
order = request.website.sale_get_order(force_create=1)
product = pool['product.product'].browse(cr, uid, int(product_id), context=context)
@@ -57,11 +60,13 @@ class website_sale_options(website_sale):
if not context.get('pricelist'):
context['pricelist'] = int(pricelist)
+ website_context = kw.get('kwargs', {}).get('context', {})
+ context = dict(context or {}, **website_context)
from_currency = pool.get('product.price.type')._get_field_currency(cr, uid, 'list_price', context)
to_currency = pricelist.currency_id
compute_currency = lambda price: pool['res.currency']._compute(cr, uid, from_currency, to_currency, price, context=context)
-
product = pool['product.product'].browse(cr, uid, int(product_id), context=context)
+ request.website = request.website.with_context(context)
return request.website._render("website_sale_options.modal", {
'product': product,
diff --git a/addons/website_sale_options/static/src/js/website_sale.js b/addons/website_sale_options/static/src/js/website_sale.js
index d1fc617cc70..39c16f020cd 100644
--- a/addons/website_sale_options/static/src/js/website_sale.js
+++ b/addons/website_sale_options/static/src/js/website_sale.js
@@ -33,6 +33,7 @@ $('.oe_website_sale #add_to_cart, .oe_website_sale #products_grid .a-submit')
var $a = $(this);
$form.ajaxSubmit({
url: '/shop/cart/update_option',
+ data: {lang: openerp.website.get_context().lang},
success: function (quantity) {
if (!$a.hasClass('js_goto_shop')) {
window.location.href = window.location.href.replace(/shop([\/?].*)?$/, "shop/cart");