From 4ce7af357317886cfd7f08e7b1e23c6a56d9f431 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Mon, 24 Nov 2014 10:51:23 +0100 Subject: [PATCH 01/16] [FIX] product: creation of reference uom Set a default value for factor when creating a new uom. Could not create a new UoM with type reference (if creates a reference uom, no need to pass a factor). Change the readonly filter to (type = bigger) to make the field writable for reference uom. This is needed to force the reset of the factor when switching of type (onchange_type). As the field was readonly, kept the old value for factor. --- addons/product/product.py | 1 + addons/product/product_view.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/product/product.py b/addons/product/product.py index ce5b9bacddf..abdbcaf4895 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -152,6 +152,7 @@ class product_uom(osv.osv): _defaults = { 'active': 1, 'rounding': 0.01, + 'factor': 1, 'uom_type': 'reference', } diff --git a/addons/product/product_view.xml b/addons/product/product_view.xml index a59a77c1357..f0908ddc46e 100644 --- a/addons/product/product_view.xml +++ b/addons/product/product_view.xml @@ -424,7 +424,7 @@ + 'readonly':[('uom_type','=','bigger')]}"/> - + - - - + + + @@ -52,14 +52,14 @@ - + - - - + + + From e2dd18f1e7304040974a81cfacce96848eb89719 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Wed, 26 Nov 2014 12:28:45 +0100 Subject: [PATCH 06/16] [FIX] mrp: prevent recursion from phantom boms with no lines if the bom is phantom and has no line, we attempt to find a new bom with the default product uom This is possible that we find the same bom that the current one In such a case, we must not explode, to avoid recursion. --- addons/mrp/mrp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mrp/mrp.py b/addons/mrp/mrp.py index 45b2bcbc047..67f36579f12 100644 --- a/addons/mrp/mrp.py +++ b/addons/mrp/mrp.py @@ -334,7 +334,7 @@ class mrp_bom(osv.osv): if bom.type == 'phantom' and not bom.bom_lines: newbom = self._bom_find(cr, uid, bom.product_id.id, bom.product_uom.id, properties) - if newbom: + if newbom and newbom != bom.id: res = self._bom_explode(cr, uid, self.browse(cr, uid, [newbom])[0], factor*bom.product_qty, properties, addthis=True, level=level+10) result = result + res[0] result2 = result2 + res[1] From f0e331e005d830d8b5aaa4cb5e2824d98bd75b2a Mon Sep 17 00:00:00 2001 From: Pierre Verkest Date: Thu, 6 Nov 2014 16:18:33 +0100 Subject: [PATCH 07/16] [FIX] correctly update many2many in listview (web client) Fixes the issue #1216 (follow the link for more information). The issue was caused by a hack in list view: the magical suffix __display is used in render_cell to determine if a many2many field should be updated. This commit simply makes sure that old many2many fields + __display keys are cleared. A better way would be to redesign/refactor the list view to avoid that hack in the first place. But this would be a much more complex task. --- addons/web/static/src/js/view_list.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index 52124d5f896..f9c857c957c 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -541,8 +541,9 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi self.records.remove(record); return; } - _(_.keys(values)).each(function(key){ - record.set(key, values[key], {silent: true}); + _.each(values, function (value, key) { + record.set(key + '__display', false, {silent: true}); + record.set(key, value, {silent: true}); }); record.trigger('change', record); }); From 117d0d0ca2460d8d840307c746e31aa7c4385a9e Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Tue, 30 Sep 2014 17:19:00 +0200 Subject: [PATCH 08/16] [FIX] sale_margin: stored field functions cannot trigger other stored field functions the update of sale.order margin field was trigger with the margin stored field of sale.order.line: This cannot work with the old api --- addons/sale_margin/sale_margin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sale_margin/sale_margin.py b/addons/sale_margin/sale_margin.py index 6329463805f..b5c1821ab98 100644 --- a/addons/sale_margin/sale_margin.py +++ b/addons/sale_margin/sale_margin.py @@ -84,7 +84,7 @@ class sale_order(osv.osv): _columns = { 'margin': fields.function(_product_margin, string='Margin', help="It gives profitability by calculating the difference between the Unit Price and the cost price.", store={ - 'sale.order.line': (_get_order, ['margin'], 20), + 'sale.order.line': (_get_order, ['margin', 'purchase_price'], 20), 'sale.order': (lambda self, cr, uid, ids, c={}: ids, ['order_line'], 20), }), } From 2461baa0dda461792d5c3f5a72cb8a98bc3877a1 Mon Sep 17 00:00:00 2001 From: Mohammad Alhashash Date: Tue, 11 Nov 2014 14:48:48 +0200 Subject: [PATCH 09/16] [FIX] stock: prevent serial number deletion on stock.move prodlot_id field may be required due to constraint `_check_tracking`. When a stock.production.lot is deleted, the constraint on linked stock.move is not checked. To avoid inconsistency, restrict the suppression. To allow the modification of existing stock.move, remove the states attribute on the field definition. As removal of serial may impact the traceability, it makes sense on buisness point of view to force the modification of previous stock.move, even if the constraint would not have been violated. The list of linked stock.move is present on the serial form view making the operation easier. Fixes #3560, lp:1176912 --- addons/stock/stock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index bd15c45e421..7dc39c150a2 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -1641,7 +1641,7 @@ class stock_move(osv.osv): 'location_dest_id': fields.many2one('stock.location', 'Destination Location', required=True,states={'done': [('readonly', True)]}, select=True, help="Location where the system will stock the finished products."), 'partner_id': fields.many2one('res.partner', 'Destination Address ', states={'done': [('readonly', True)]}, help="Optional address where goods are to be delivered, specifically used for allotment"), - 'prodlot_id': fields.many2one('stock.production.lot', 'Serial Number', states={'done': [('readonly', True)]}, help="Serial number is used to put a serial number on the production", select=True), + 'prodlot_id': fields.many2one('stock.production.lot', 'Serial Number', help="Serial number is used to put a serial number on the production", select=True, ondelete='restrict'), 'tracking_id': fields.many2one('stock.tracking', 'Pack', select=True, states={'done': [('readonly', True)]}, help="Logistical shipping unit: pallet, box, pack ..."), 'auto_validate': fields.boolean('Auto Validate'), From 043f7b84b83ec0c40274af30e2731b1c05c3d520 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Wed, 12 Nov 2014 13:56:41 -0500 Subject: [PATCH 10/16] [FIX] base: avoid having 'False' in name of a bank The name_get of res.partner.bank uses the format_layout to generate the name of the bank. As every field is not required, we may get 'False' in the name. Replace these missing values by an empty string. Fixes #3590 --- openerp/addons/base/res/res_bank.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openerp/addons/base/res/res_bank.py b/openerp/addons/base/res/res_bank.py index 00d18d08078..aa9fa861548 100644 --- a/openerp/addons/base/res/res_bank.py +++ b/openerp/addons/base/res/res_bank.py @@ -190,6 +190,7 @@ class res_partner_bank(osv.osv): try: if not data.get('bank_name'): data['bank_name'] = _('BANK') + data = dict((k, v or '') for (k, v) in data.iteritems()) name = bank_code_format[data['state']] % data except Exception: raise osv.except_osv(_("Formating Error"), _("Invalid Bank Account Type Name format.")) From 2080ea0f0acfb0e03372e8abd264a5fb72d7d431 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Thu, 27 Nov 2014 13:25:54 +0100 Subject: [PATCH 11/16] [FIX] stock: on stock picking invoicing, allow to pick a journal if no move line --- .../stock/wizard/stock_invoice_onshipping.py | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/addons/stock/wizard/stock_invoice_onshipping.py b/addons/stock/wizard/stock_invoice_onshipping.py index 7c960c5e617..ed559d1e341 100644 --- a/addons/stock/wizard/stock_invoice_onshipping.py +++ b/addons/stock/wizard/stock_invoice_onshipping.py @@ -46,23 +46,24 @@ class stock_invoice_onshipping(osv.osv_memory): browse_picking = model_pool.browse(cr, uid, res_ids, context=context) for pick in browse_picking: - if not pick.move_lines: - continue - src_usage = pick.move_lines[0].location_id.usage - dest_usage = pick.move_lines[0].location_dest_id.usage - type = pick.type - if type == 'out' and dest_usage == 'supplier': - journal_type = 'purchase_refund' - elif type == 'out' and dest_usage == 'customer': - journal_type = 'sale' - elif type == 'in' and src_usage == 'supplier': - journal_type = 'purchase' - elif type == 'in' and src_usage == 'customer': - journal_type = 'sale_refund' - else: - journal_type = 'sale' + domain = [('type', 'in', ['sale', 'sale_refund', 'purchase', 'purchase_refund'])] + if pick.move_lines: + src_usage = pick.move_lines[0].location_id.usage + dest_usage = pick.move_lines[0].location_dest_id.usage + type = pick.type + if type == 'out' and dest_usage == 'supplier': + journal_type = 'purchase_refund' + elif type == 'out' and dest_usage == 'customer': + journal_type = 'sale' + elif type == 'in' and src_usage == 'supplier': + journal_type = 'purchase' + elif type == 'in' and src_usage == 'customer': + journal_type = 'sale_refund' + else: + journal_type = 'sale' + domain = [('type', '=', journal_type)] - value = journal_obj.search(cr, uid, [('type', '=',journal_type )]) + value = journal_obj.search(cr, uid, domain) for jr_type in journal_obj.browse(cr, uid, value, context=context): t1 = jr_type.id,jr_type.name if t1 not in vals: From fc85a7ee5c33eadc22652b6b26478c320e35b283 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Thu, 27 Nov 2014 12:36:28 +0100 Subject: [PATCH 12/16] [FIX] product: remove unnecessary UoM rounding step, add missing test Remove the intermediate rounding inside _compute_qty(), as it is not necessary after rev. fa2f7b86 and has undesired side-effects. An extra float_round() operation inside _compute_qty() had been added at rev. 311c77bb to avoid a float representation error in UoM factors that could bias the ceiling() operation done as the last conversion step. Example 1: Dozen has a factor of 1/12, which was previously stored in the database with a decimal accuracy of 12 significant decimal digits. This meant the factor was exactly stored as 0.08333333333333. When reading this back into a Python float, the precision was not sufficient, and the UoM conversion of 1 Dozen to Units gave a result of 12.00000000000047961... After the final ceiling() operation to Unit's rounding, the converted value ended up as 13. This problem was initially solved using an extra rounding. However at revision fa2f7b86 the decimal precision used to store UoM factors was increased to preserve all significant digits. This added the extra precision necessary to read the Dozen factor back into an accurate float value of 1/12, and the conversion of 1 Dozen now gives 12.0 Units, even without the intermediate rounding operation. (Works for other factor values too) At the same time that extra rounding operation has undesired side-effects, as it requires a fixed precision derived from the rounding precisions of the UoMs. But there is no given precision that would work in all cases for this intermediate value. It is always possible to find a valid combination of UoM roundings that breaks that intermediate step, e.g. by forcing integer roundings. Example 2: Let Grams have a rounding precision set to 1 because no smaller quantities are allowed, and Kilograms a rounding of 0.001 to allow representing 1 Gram. (gram factor = 1000 and kilogram rounding = .001 by default) If we try to convert 1234 Grams into Kilograms, the extra rounding introduced in 311c77bb will cause a rounding of 1234.0/1000.0 at the precision of Grams (1), which gives 1.0 as a result. The net result of this conversion gives 1234.0 Gram = 1.0 Kilogram, while the correct result (1.234 Kilogram) is perfectly compatible with the UoM settings. Similar errors could be triggered with various rounding settings, as long as the intermediate rounding needs a finite precision. Two extra tests have been added to cover Example 1 and Example 2. -- Related to #2072, #1125, #1126, #2672 Closes #2495, #2498 --- addons/product/product.py | 5 +---- addons/product/product_data.xml | 1 + addons/product/tests/test_uom.py | 17 +++++++++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/addons/product/product.py b/addons/product/product.py index abdbcaf4895..69dc23b3fb6 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -178,10 +178,7 @@ class product_uom(osv.osv): raise osv.except_osv(_('Error!'), _('Conversion from Product UoM %s to Default UoM %s is not possible as they both belong to different Category!.') % (from_unit.name,to_unit.name,)) else: return qty - # First round to the precision of the original unit, so that - # float representation errors do not bias the following ceil() - # e.g. with 1 / (1/12) we could get 12.0000048, ceiling to 13! - amount = float_round(qty/from_unit.factor, precision_rounding=from_unit.rounding) + amount = qty/from_unit.factor if to_unit: amount = ceiling(amount * to_unit.factor, to_unit.rounding) return amount diff --git a/addons/product/product_data.xml b/addons/product/product_data.xml index 49362e380f4..4e62e1e0b3f 100644 --- a/addons/product/product_data.xml +++ b/addons/product/product_data.xml @@ -47,6 +47,7 @@ kg + diff --git a/addons/product/tests/test_uom.py b/addons/product/tests/test_uom.py index 3d3ba04b689..e6ac6055137 100644 --- a/addons/product/tests/test_uom.py +++ b/addons/product/tests/test_uom.py @@ -12,7 +12,10 @@ class TestUom(TransactionCase): def test_10_conversion(self): cr, uid = self.cr, self.uid gram_id = self.imd.get_object_reference(cr, uid, 'product', 'product_uom_gram')[1] + kg_id = self.imd.get_object_reference(cr, uid, 'product', 'product_uom_kgm')[1] tonne_id = self.imd.get_object_reference(cr, uid, 'product', 'product_uom_ton')[1] + unit_id = self.imd.get_object_reference(cr, uid, 'product','product_uom_unit')[1] + dozen_id = self.imd.get_object_reference(cr, uid, 'product','product_uom_dozen')[1] qty = self.uom._compute_qty(cr, uid, gram_id, 1020000, tonne_id) self.assertEquals(qty, 1.02, "Converted quantity does not correspond.") @@ -20,6 +23,20 @@ class TestUom(TransactionCase): price = self.uom._compute_price(cr, uid, gram_id, 2, tonne_id) self.assertEquals(price, 2000000.0, "Converted price does not correspond.") + # If the conversion factor for Dozens (1/12) is not stored with sufficient precision, + # the conversion of 1 Dozen into Units will give e.g. 12.00000000000047 Units + # and the Unit rounding will round that up to 13. + # This is a partial regression test for rev. 311c77bb, which is further improved + # by rev. fa2f7b86. + qty = self.uom._compute_qty(cr, uid, dozen_id, 1, unit_id) + self.assertEquals(qty, 12.0, "Converted quantity does not correspond.") + + # Regression test for side-effect of commit 311c77bb - converting 1234 Grams + # into Kilograms should work even if grams are rounded to 1. + self.uom.write(cr, uid, gram_id, {'rounding': 1}) + qty = self.uom._compute_qty(cr, uid, gram_id, 1234, kg_id) + self.assertEquals(qty, 1.234, "Converted quantity does not correspond.") + def test_20_rounding(self): cr, uid = self.cr, self.uid unit_id = self.imd.get_object_reference(cr, uid, 'product', 'product_uom_unit')[1] From de07c642b67d8dbca11086ad3f9cbaa9928c6077 Mon Sep 17 00:00:00 2001 From: Andrius Preimantas Date: Fri, 28 Nov 2014 11:39:36 +0100 Subject: [PATCH 13/16] [FIX] base: delete parent partner reset use_parent_address When deleting a partner with some contacts, if the contacts had selected the "use parent address" checkbox, the address of the contacts was stucked in readonly mode (no checkbox to disable it). Disable use_parent_address for orphan partners. Fixes #3611 #3613 --- openerp/addons/base/res/res_partner.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index 22cd4a8d66a..7d357ed0c9e 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -508,6 +508,14 @@ class res_partner(osv.osv, format_address): if not parent.is_company: parent.write({'is_company': True}) + def unlink(self, cr, uid, ids, context=None): + orphan_contact_ids = self.search(cr, uid, + [('parent_id', 'in', ids), ('id', 'not in', ids), ('use_parent_address', '=', True)], context=context) + if orphan_contact_ids: + # no longer have a parent address + self.write(cr, uid, orphan_contact_ids, {'use_parent_address': False}, context=context) + return super(res_partner, self).unlink(cr, uid, ids, context=context) + def write(self, cr, uid, ids, vals, context=None): if isinstance(ids, (int, long)): ids = [ids] From cdce2e22837e6beafb3f2d8584f8c125bdaba596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Bellier?= Date: Fri, 31 Oct 2014 15:07:08 +0100 Subject: [PATCH 14/16] [FIX] stock: Never hide the invoice control on pickings Invoice control field was invisible, and we cannot change value if none selected or copy picking in. Fixes #3636 --- addons/stock/stock_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml index 6a58107d7fe..74d9c76d016 100644 --- a/addons/stock/stock_view.xml +++ b/addons/stock/stock_view.xml @@ -765,7 +765,7 @@ - + From 29f895f947d5aaf7d267f7bd884712d93ca31194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Mon, 1 Dec 2014 10:29:00 +0100 Subject: [PATCH 15/16] [FIX] fix editing one2many in some cases (web) See issue #3964 for more detail. Main problem was caused by commit f0e331e005d8. It set the key name+'__display' to false when reloading a record for all field types, but it was only concerned with many2many. --- addons/web/static/src/js/view_list.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index f9c857c957c..3a3e8d55848 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -528,6 +528,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi }, reload_record: function (record) { var self = this; + var fields = this.fields_view.fields; // Use of search_read instead of read to check if we can still read the record (security rules) return this.dataset.read_ids( [record.get('id')], @@ -542,7 +543,8 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi return; } _.each(values, function (value, key) { - record.set(key + '__display', false, {silent: true}); + if (fields[key] && fields[key].type === 'many2many') + record.set(key + '__display', false, {silent: true}); record.set(key, value, {silent: true}); }); record.trigger('change', record); From 419d934143423c524fa5d5f9c6257ee4231b8502 Mon Sep 17 00:00:00 2001 From: Eddy Ernesto del Valle Pino Date: Thu, 20 Nov 2014 13:38:40 -0500 Subject: [PATCH 16/16] [FIX] base_calendar: meetings email notification recipients Reminder emails are generated based on the list of attendees. The email_to field used to be a string with a list of emails separated by spaces while the comma is the valid separator (RFC2822). Fixes #3933 #3784 #2033 --- addons/base_calendar/base_calendar.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/addons/base_calendar/base_calendar.py b/addons/base_calendar/base_calendar.py index 0f7ef0c45b0..64053bba712 100644 --- a/addons/base_calendar/base_calendar.py +++ b/addons/base_calendar/base_calendar.py @@ -839,8 +839,7 @@ class calendar_alarm(osv.osv): current_datetime = datetime.now() alarm_ids = self.search(cr, uid, [('state', '!=', 'done')], context=context) - mail_to = "" - + mail_to = set() for alarm in self.browse(cr, uid, alarm_ids, context=context): next_trigger_date = None update_vals = {} @@ -890,10 +889,12 @@ From: """ % (alarm.name, alarm.trigger_date, alarm.description, \ alarm.user_id.name, alarm.user_id.signature) - mail_to = alarm.user_id.email + mail_to.add(alarm.user_id.email) for att in alarm.attendee_ids: - mail_to = mail_to + " " + att.user_id.email + if att.user_id.email: + mail_to.add(att.user_id.email) if mail_to: + mail_to = ','.join(mail_to) vals = { 'state': 'outgoing', 'subject': sub, @@ -1121,7 +1122,7 @@ rule or repeating pattern of time to exclude from the recurring rule."), for att in event.attendee_ids: attendees[att.partner_id.id] = True new_attendees = [] - mail_to = "" + mail_to = set() for partner in event.partner_ids: if partner.id in attendees: continue @@ -1134,13 +1135,14 @@ rule or repeating pattern of time to exclude from the recurring rule."), 'email': partner.email }, context=local_context) if partner.email: - mail_to = mail_to + " " + partner.email + mail_to.add(partner.email) self.write(cr, uid, [event.id], { 'attendee_ids': [(4, att_id)] }, context=context) new_attendees.append(att_id) if mail_to and current_user.email: + mail_to = ','.join(mail_to) att_obj._send_mail(cr, uid, new_attendees, mail_to, email_from = current_user.email, context=context) return True