[MERGE] forward port of branch 9.0 up to e8b965f

This commit is contained in:
Christophe Simonis
2016-03-16 16:09:13 +01:00
19 changed files with 113 additions and 34 deletions
+1 -1
View File
@@ -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)
+5
View File
@@ -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):
+2 -2
View File
@@ -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():
@@ -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();
}
});
+1 -1
View File
@@ -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;
+2 -9
View File
@@ -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'),
+3 -3
View File
@@ -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
+2 -1
View File
@@ -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:
@@ -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;
@@ -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);
@@ -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;
+25 -1
View File
@@ -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)
@@ -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');
+3 -3
View File
@@ -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
''''''''''''''
+13
View File
@@ -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)]
+16
View File
@@ -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:
+5 -3
View File
@@ -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', '')
+5 -3
View File
@@ -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
+1 -1
View File
@@ -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')