diff --git a/addons/gamification/models/challenge.py b/addons/gamification/models/challenge.py index 9662cccfe5e..1a5446247f9 100644 --- a/addons/gamification/models/challenge.py +++ b/addons/gamification/models/challenge.py @@ -259,7 +259,7 @@ class gamification_challenge(osv.Model): # close scheduled challenges planned_challenge_ids = self.search(cr, uid, [ ('state', '=', 'inprogress'), - ('end_date', '>=', fields.date.today())]) + ('end_date', '<', fields.date.today())]) if planned_challenge_ids: self.write(cr, uid, planned_challenge_ids, {'state': 'done'}, context=context) diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index 4496eef23cf..41ca1320802 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -1392,6 +1392,7 @@ class pos_order_line(osv.osv): def _amount_line_all(self, cr, uid, ids, field_names, arg, context=None): res = dict([(i, {}) for i in ids]) account_tax_obj = self.pool.get('account.tax') + cur_obj = self.pool.get('res.currency') for line in self.browse(cr, uid, ids, context=context): cur = line.order_id.pricelist_id.currency_id taxes = [ tax for tax in line.tax_ids if tax.company_id.id == line.order_id.company_id.id ] @@ -1405,6 +1406,10 @@ class pos_order_line(osv.osv): taxes = account_tax_obj.browse(cr, uid, taxes_ids, context).compute_all(price, cur, line.qty, product=line.product_id, partner=line.order_id.partner_id or False) res[line.id]['price_subtotal'] = taxes['total_excluded'] res[line.id]['price_subtotal_incl'] = taxes['total_included'] + + res[line.id]['price_subtotal'] = cur_obj.round(cr, uid, cur, res[line.id]['price_subtotal']) + res[line.id]['price_subtotal_incl'] = cur_obj.round(cr, uid, cur, res[line.id]['price_subtotal_incl']) + return res def onchange_product_id(self, cr, uid, ids, pricelist, product_id, qty=0, partner_id=False, context=None): diff --git a/addons/point_of_sale/report/pos_details.py b/addons/point_of_sale/report/pos_details.py index 2e6dfa13095..3e77f35a027 100644 --- a/addons/point_of_sale/report/pos_details.py +++ b/addons/point_of_sale/report/pos_details.py @@ -31,8 +31,8 @@ class pos_details(report_sxw.rml_parse): result = {} user_ids = form['user_ids'] or self._get_all_users() company_id = user_obj.browse(self.cr, self.uid, self.uid).company_id.id - user = self.pool['res.users'].browse(self.cr, self.uid, self.uid) or self.localcontext.get('tz') or 'UTC' - tz_name = user.tz + user = self.pool['res.users'].browse(self.cr, self.uid, self.uid) + tz_name = user.tz or self.localcontext.get('tz') or 'UTC' user_tz = pytz.timezone(tz_name) between_dates = {} for date_field, delta in {'date_start': {'days': 0}, 'date_end': {'days': 1}}.items(): diff --git a/addons/point_of_sale/static/src/js/chrome.js b/addons/point_of_sale/static/src/js/chrome.js index ea2febfbf23..f260dd03d71 100644 --- a/addons/point_of_sale/static/src/js/chrome.js +++ b/addons/point_of_sale/static/src/js/chrome.js @@ -478,8 +478,6 @@ var Chrome = PosBaseWidget.extend({ build_chrome: function() { var self = this; FastClick.attach(document.body); - core.bus.trigger('set_full_screen', true); - this.renderElement(); this.$('.pos-logo').click(function(){ @@ -752,7 +750,6 @@ var Chrome = PosBaseWidget.extend({ destroy: function() { this.pos.destroy(); - core.bus.trigger('set_full_screen', false); this._super(); } }); diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index dd713175f13..b0513e53f8e 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -1322,7 +1322,7 @@ exports.Orderline = Backbone.Model.extend({ _compute_all: function(tax, base_amount, quantity) { if (tax.amount_type === 'fixed') { var ret = tax.amount * quantity; - return base_amount >= 0 ? ret : ret * -1; + return base_amount > 0 ? ret : ret * -1; } if ((tax.amount_type === 'percent' && !tax.price_include) || (tax.amount_type === 'division' && tax.price_include)){ return base_amount * tax.amount / 100; diff --git a/addons/product_expiry/product_expiry.py b/addons/product_expiry/product_expiry.py index daddbae7e4e..25390891aa5 100644 --- a/addons/product_expiry/product_expiry.py +++ b/addons/product_expiry/product_expiry.py @@ -39,16 +39,9 @@ class stock_production_lot(osv.osv): } # Assign dates according to products data def create(self, cr, uid, vals, context=None): - newid = super(stock_production_lot, self).create(cr, uid, vals, context=context) - obj = self.browse(cr, uid, newid, context=context) - towrite = [] - for f in ('life_date', 'use_date', 'removal_date', 'alert_date'): - if not getattr(obj, f): - towrite.append(f) context = dict(context or {}) - context['product_id'] = obj.product_id.id - self.write(cr, uid, [obj.id], self.default_get(cr, uid, towrite, context=context)) - return newid + context['product_id'] = vals.get('product_id', context.get('default_product_id')) + return super(stock_production_lot, self).create(cr, uid, vals, context=context) _defaults = { 'life_date': _get_date('life_time'), diff --git a/addons/purchase/report/purchase_report.py b/addons/purchase/report/purchase_report.py index 59381718b1b..a1a6cb4e3b2 100644 --- a/addons/purchase/report/purchase_report.py +++ b/addons/purchase/report/purchase_report.py @@ -70,10 +70,10 @@ class purchase_report(osv.osv): extract(epoch from age(s.date_approve,s.date_order))/(24*60*60)::decimal(16,2) as delay, extract(epoch from age(l.date_planned,s.date_order))/(24*60*60)::decimal(16,2) as delay_pass, count(*) as nbr, - sum(l.price_unit * COALESCE(cr.rate, 1.0) * l.product_qty)::decimal(16,2) as price_total, - avg(100.0 * (l.price_unit * COALESCE(cr.rate,1.0) * l.product_qty) / NULLIF(ip.value_float*l.product_qty/u.factor*u2.factor, 0.0))::decimal(16,2) as negociation, + sum(l.price_unit / COALESCE(cr.rate, 1.0) * l.product_qty)::decimal(16,2) as price_total, + avg(100.0 * (l.price_unit / COALESCE(cr.rate,1.0) * l.product_qty) / NULLIF(ip.value_float*l.product_qty/u.factor*u2.factor, 0.0))::decimal(16,2) as negociation, sum(ip.value_float*l.product_qty/u.factor*u2.factor)::decimal(16,2) as price_standard, - (sum(l.product_qty * COALESCE(cr.rate, 1.0) * l.price_unit)/NULLIF(sum(l.product_qty/u.factor*u2.factor),0.0))::decimal(16,2) as price_average, + (sum(l.product_qty * l.price_unit / COALESCE(cr.rate, 1.0))/NULLIF(sum(l.product_qty/u.factor*u2.factor),0.0))::decimal(16,2) as price_average, partner.country_id as country_id, partner.commercial_partner_id as commercial_partner_id, analytic_account.id as account_analytic_id diff --git a/addons/stock/stock.py b/addons/stock/stock.py index a30ba422062..5008c831b08 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1391,7 +1391,8 @@ class stock_picking(models.Model): prod2move_ids = {} still_to_do = [] #make a dictionary giving for each product, the moves and related quantity that can be used in operation links - for move in [x for x in picking.move_lines if x.state not in ('done', 'cancel')]: + moves = sorted([x for x in picking.move_lines if x.state not in ('done', 'cancel')], key=lambda x: (((x.state == 'assigned') and -2 or 0) + (x.partially_available and -1 or 0))) + for move in moves: if not prod2move_ids.get(move.product_id.id): prod2move_ids[move.product_id.id] = [{'move': move, 'remaining_qty': move.product_qty}] else: diff --git a/addons/web/static/src/js/framework/pyeval.js b/addons/web/static/src/js/framework/pyeval.js index ce25e7af534..2b553d37e0f 100644 --- a/addons/web/static/src/js/framework/pyeval.js +++ b/addons/web/static/src/js/framework/pyeval.js @@ -757,6 +757,12 @@ var wrapping_dict = py.type('wrapping_dict', null, { __getattr__: function (key) { return this.__getitem__(py.str.fromJSON(key)); }, + __len__: function () { + return Object.keys(this._store).length + }, + __nonzero__: function () { + return py.PY_size(this) > 0 ? py.True : py.False; + }, get: function () { var args = py.PY_parseArgs(arguments, ['k', ['d', py.None]]); @@ -780,6 +786,12 @@ var wrapping_list = py.type('wrapping_list', null, { __getitem__: function (index) { return wrap(this._store[index.toJSON()]); }, + __len__: function () { + return this._store.length; + }, + __nonzero__: function () { + return py.PY_size(this) > 0 ? py.True : py.False; + }, fromJSON: function (ar) { var instance = py.PY_call(wrapping_list); instance._store = ar; diff --git a/addons/web/static/src/js/views/form_view.js b/addons/web/static/src/js/views/form_view.js index 8b5905897b5..3f03c288841 100644 --- a/addons/web/static/src/js/views/form_view.js +++ b/addons/web/static/src/js/views/form_view.js @@ -74,6 +74,7 @@ var FormView = View.extend(common.FieldManagerMixin, { "footer_to_buttons": false, }); this.is_initialized = $.Deferred(); + this.record_loaded = $.Deferred(); this.mutating_mutex = new utils.Mutex(); this.save_list = []; this.render_value_defs = []; @@ -361,6 +362,7 @@ var FormView = View.extend(common.FieldManagerMixin, { this._actualize_mode(); this.set({ 'title' : record.id ? record.display_name : _t("New") }); + this.record_loaded = $.Deferred(); _(this.fields).each(function (field, f) { field._dirty_flag = false; field._inhibit_on_change_flag = true; @@ -376,6 +378,7 @@ var FormView = View.extend(common.FieldManagerMixin, { self.on_form_changed(); self.rendering_engine.init_fields(); self.is_initialized.resolve(); + self.record_loaded.resolve(); self.do_update_pager(record.id === null || record.id === undefined); if (self.sidebar) { self.sidebar.do_attachement_update(self.dataset, self.datarecord.id); diff --git a/addons/web/static/src/js/views/form_widgets.js b/addons/web/static/src/js/views/form_widgets.js index fbc8a28e375..92782b5923a 100644 --- a/addons/web/static/src/js/views/form_widgets.js +++ b/addons/web/static/src/js/views/form_widgets.js @@ -436,6 +436,20 @@ var FieldCharDomain = common.AbstractField.extend(common.ReinitializeFieldMixin, this._super.apply(this, arguments); this.debug = session.debug; }, + start: function() { + var self = this; + var tmp = this._super(); + if (this.options.model_field){ + this.field_manager.fields[this.options.model_field].on("change:value", this, function(){ + if (self.view && self.view.record_loaded.state == "resolved" && self.view.onchanges_mutex){ + self.view.onchanges_mutex.def.then(function(){ + self.render_value(); + }); + } + }); + } + return tmp; + }, render_value: function() { var self = this; diff --git a/addons/website/models/ir_ui_view.py b/addons/website/models/ir_ui_view.py index 20200531a63..6d02a2d30ec 100644 --- a/addons/website/models/ir_ui_view.py +++ b/addons/website/models/ir_ui_view.py @@ -2,6 +2,7 @@ import copy import logging +from itertools import groupby from lxml import etree, html from openerp import SUPERUSER_ID, api, tools @@ -31,6 +32,29 @@ class view(osv.osv): self.clear_caches() return res + def _sort_suitability_key(self): + """ + Key function to sort views by descending suitability + Suitability of a view is defined as follow: + + * if the view and request website_id are matched + * then if the view has no set website + """ + context_website_id = self.env.context.get('website_id', 1) + website_id = self.website_id.id or 0 + different_website = context_website_id != website_id + + return (different_website, website_id) + + def filter_duplicate(self): + """ + Filter current recordset only keeping the most suitable view per distinct key + """ + filtered = self.browse([]) + for _, group in groupby(self, key=lambda r:r.key): + filtered += sorted(group, key=lambda r:r._sort_suitability_key())[0] + return filtered + def _view_obj(self, cr, uid, view_id, context=None): if isinstance(view_id, basestring): if 'website_id' in (context or {}): @@ -39,7 +63,7 @@ class view(osv.osv): else: rec_id = self.search(cr, uid, [('key', '=', view_id)], context=context) if rec_id: - return self.browse(cr, uid, rec_id, context=context)[0] + return self.browse(cr, uid, rec_id, context=context).filter_duplicate() else: return self.pool['ir.model.data'].xmlid_to_object( cr, uid, view_id, raise_if_not_found=True, context=context) diff --git a/addons/website_payment/static/src/js/payment_acquirer.js b/addons/website_payment/static/src/js/payment_acquirer.js index 56bcd8caf6b..d7397f1b04d 100644 --- a/addons/website_payment/static/src/js/payment_acquirer.js +++ b/addons/website_payment/static/src/js/payment_acquirer.js @@ -7,7 +7,6 @@ $(document).ready(function () { $('input#cc_number').on('focusout', function (e) { var valid_value = $.payment.validateCardNumber(this.value); var card_type = $.payment.cardType(this.value); - console.log('Validating card', this.value, 'is a', card_type, 'and valid:', valid_value); if (card_type) { $(this).parent('.form-group').children('.card_placeholder').removeClass().addClass('card_placeholder ' + card_type); $(this).parent('.form-group').children('input[name="cc_brand"]').val(card_type) @@ -29,7 +28,6 @@ $(document).ready(function () { var cc_nbr = $(this).parents('.oe_cc').find('#cc_number').val(); var card_type = $.payment.cardType(cc_nbr); var valid_value = $.payment.validateCardCVC(this.value, card_type); - console.log('Validating CVC', this.value, 'for card', cc_nbr, 'of type', card_type, 'and is valid:', valid_value); if (valid_value) { $(this).parent('.form-group').addClass('has-success'); $(this).parent('.form-group').removeClass('has-error'); @@ -45,7 +43,6 @@ $(document).ready(function () { var month = expiry_value.month || ''; var year = expiry_value.year || ''; var valid_value = $.payment.validateCardExpiry(month, year); - console.log('Validating expiry', this.value, 'month', month, 'year', year, 'and is valid:', valid_value); if (valid_value) { $(this).parent('.form-group').addClass('has-success'); $(this).parent('.form-group').removeClass('has-error'); diff --git a/doc/reference/orm.rst b/doc/reference/orm.rst index 62ef4a3d018..10ac368e950 100644 --- a/doc/reference/orm.rst +++ b/doc/reference/orm.rst @@ -376,9 +376,9 @@ it uses the values of other *fields*, it should specify those fields using def _apply_discount(self): for record in self: # compute actual discount from discount percentage - discount = self.value * self.discount - self.discount_value = discount - self.total = self.value - discount + discount = record.value * record.discount + record.discount_value = discount + record.total = record.value - discount Related fields '''''''''''''' diff --git a/openerp/addons/base/ir/ir_actions.py b/openerp/addons/base/ir/ir_actions.py index 5ce474c0187..0cd3ca56343 100644 --- a/openerp/addons/base/ir/ir_actions.py +++ b/openerp/addons/base/ir/ir_actions.py @@ -1165,6 +1165,19 @@ Launch Manually Once: after having been launched manually, it sets automatically } _order="sequence,id" + @openerp.api.multi + def unlink(self): + if self: + try: + todo_open_menu = self.env.ref('base.open_menu') + # don't remove base.open_menu todo but set its original action + if todo_open_menu in self: + todo_open_menu.action_id = self.env.ref('base.action_client_base_menu').id + self -= todo_open_menu + except ValueError: + pass + return super(ir_actions_todo, self).unlink() + def name_get(self, cr, uid, ids, context=None): return [(rec.id, rec.action_id.name) for rec in self.browse(cr, uid, ids, context=context)] diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py index 5fd40315cd8..8c4e2edb38d 100644 --- a/openerp/addons/base/ir/ir_model.py +++ b/openerp/addons/base/ir/ir_model.py @@ -333,6 +333,22 @@ class ir_model_fields(osv.osv): self.readonly = True self.copy = False + @api.constrains('depends') + def _check_depends(self): + """ Check whether all fields in dependencies are valid. """ + for record in self: + for seq in (record.depends or "").split(","): + model = self.env[record.model] + names = seq.strip().split(".") + last = len(names) - 1 + for index, name in enumerate(names): + field = model._fields.get(name) + if field is None: + raise UserError(_("Unknown field %r in dependency %r") % (name, seq.strip())) + if index < last and not field.relational: + raise UserError(_("Non-relational field %r in dependency %r") % (name, seq.strip())) + model = model[name] + @api.onchange('compute') def _onchange_compute(self): if self.compute: diff --git a/openerp/addons/base/ir/ir_ui_view.py b/openerp/addons/base/ir/ir_ui_view.py index ac414229cd1..7493f132139 100644 --- a/openerp/addons/base/ir/ir_ui_view.py +++ b/openerp/addons/base/ir/ir_ui_view.py @@ -867,6 +867,8 @@ class view(osv.osv): if Model is None: self.raise_view_error(cr, user, _('Model not found: %(model)s') % dict(model=model), view_id, context) + is_base_model = context.get('base_model_name', model) == model + if node.tag == 'diagram': if node.getchildren()[0].tag == 'node': node_model = self.pool[node.getchildren()[0].get('object')] @@ -874,7 +876,7 @@ class view(osv.osv): fields.update(node_fields) if not node.get("create") and \ not node_model.check_access_rights(cr, user, 'create', raise_exception=False) or \ - not context.get("create", True): + not context.get("create", True) and is_base_model: node.set("create", 'false') if node.getchildren()[1].tag == 'arrow': arrow_fields = self.pool[node.getchildren()[1].get('object')].fields_get(cr, user, None, context=context) @@ -889,7 +891,7 @@ class view(osv.osv): for action, operation in (('create', 'create'), ('delete', 'unlink'), ('edit', 'write')): if not node.get(action) and \ not Model.check_access_rights(cr, user, operation, raise_exception=False) or \ - not context.get(action, True): + not context.get(action, True) and is_base_model: node.set(action, 'false') if node.tag in ('kanban'): group_by_name = node.get('default_group_by') @@ -900,7 +902,7 @@ class view(osv.osv): for action, operation in (('group_create', 'create'), ('group_delete', 'unlink'), ('group_edit', 'write')): if not node.get(action) and \ not group_by_model.check_access_rights(cr, user, operation, raise_exception=False) or \ - not context.get(action, True): + not context.get(action, True) and is_base_model: node.set(action, 'false') arch = etree.tostring(node, encoding="utf-8").replace('\t', '') diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index f3459e090b8..37bf7fcce05 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -51,10 +51,12 @@ class format_address(object): doc = etree.fromstring(arch) for node in doc.xpath("//div[@class='address_format']"): tree = etree.fromstring(v % {'city': _('City'), 'zip': _('ZIP'), 'state': _('State')}) - for child in node.xpath("//field"): - if child.attrib.get('modifiers'): - for field in tree.xpath("//field[@name='%s']" % child.attrib.get('name')): + for child in node.xpath(".//field"): + for field in tree.xpath("//field[@name='%s']" % child.attrib.get("name")): + if child.attrib.get("modifiers"): field.attrib['modifiers'] = child.attrib.get('modifiers') + if child.attrib.get("on_change"): + field.attrib["on_change"] = child.attrib.get("on_change") node.getparent().replace(node, tree) arch = etree.tostring(doc) break diff --git a/openerp/cli/scaffold.py b/openerp/cli/scaffold.py index 3f86965fb9a..221f192347e 100644 --- a/openerp/cli/scaffold.py +++ b/openerp/cli/scaffold.py @@ -126,7 +126,7 @@ class template(object): if ext not in ('.py', '.xml', '.csv', '.js', '.rst', '.html', '.template'): f.write(content) else: - env.from_string(content)\ + env.from_string(content.decode('utf-8'))\ .stream(params or {})\ .dump(f, encoding='utf-8')