From f7b9046c7b57272e7504ad19137515951c0e9194 Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Fri, 20 Feb 2015 11:16:45 +0100 Subject: [PATCH 01/19] Add a failing test to reproduce #5398 --- openerp/addons/test_inherits/__init__.py | 2 + openerp/addons/test_inherits/__openerp__.py | 16 ++++++++ openerp/addons/test_inherits/demo_data.xml | 19 +++++++++ .../addons/test_inherits/ir.model.access.csv | 4 ++ openerp/addons/test_inherits/models.py | 41 +++++++++++++++++++ .../addons/test_inherits/tests/__init__.py | 3 ++ .../test_inherits/tests/test_inherits.py | 17 ++++++++ 7 files changed, 102 insertions(+) create mode 100644 openerp/addons/test_inherits/__init__.py create mode 100644 openerp/addons/test_inherits/__openerp__.py create mode 100644 openerp/addons/test_inherits/demo_data.xml create mode 100644 openerp/addons/test_inherits/ir.model.access.csv create mode 100644 openerp/addons/test_inherits/models.py create mode 100644 openerp/addons/test_inherits/tests/__init__.py create mode 100644 openerp/addons/test_inherits/tests/test_inherits.py diff --git a/openerp/addons/test_inherits/__init__.py b/openerp/addons/test_inherits/__init__.py new file mode 100644 index 00000000000..a0fdc10fe11 --- /dev/null +++ b/openerp/addons/test_inherits/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import models diff --git a/openerp/addons/test_inherits/__openerp__.py b/openerp/addons/test_inherits/__openerp__.py new file mode 100644 index 00000000000..21010738f8e --- /dev/null +++ b/openerp/addons/test_inherits/__openerp__.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +{ + 'name': 'test-inherits', + 'version': '0.1', + 'category': 'Tests', + 'description': """A module to verify the inheritance using _inherits.""", + 'author': 'Camptocamp', + 'website': 'http://www.camptocamp.com', + 'depends': ['base'], + 'data': [ + 'ir.model.access.csv', + 'demo_data.xml', + ], + 'installable': True, + 'auto_install': False, +} diff --git a/openerp/addons/test_inherits/demo_data.xml b/openerp/addons/test_inherits/demo_data.xml new file mode 100644 index 00000000000..98b19095acd --- /dev/null +++ b/openerp/addons/test_inherits/demo_data.xml @@ -0,0 +1,19 @@ + + + + Unit A + a + + + + + A + + + + + A + + + + diff --git a/openerp/addons/test_inherits/ir.model.access.csv b/openerp/addons/test_inherits/ir.model.access.csv new file mode 100644 index 00000000000..22ecf366e31 --- /dev/null +++ b/openerp/addons/test_inherits/ir.model.access.csv @@ -0,0 +1,4 @@ +"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" +access_test_unit,access_test_unit,model_test_unit,,1,1,1,1 +access_test_box,access_test_box,model_test_box,,1,1,1,1 +access_test_pallet,access_test_pallet,model_test_pallet,,1,1,1,1 diff --git a/openerp/addons/test_inherits/models.py b/openerp/addons/test_inherits/models.py new file mode 100644 index 00000000000..c8f86ec52d7 --- /dev/null +++ b/openerp/addons/test_inherits/models.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +from openerp import models, fields, api, osv + + +# We just create a new model +class Unit(models.Model): + _name = 'test.unit' + + _columns = { + 'name': osv.fields.char('Name', required=True), + 'state': osv.fields.selection([('a', 'A'), ('b', 'B')], + string='State'), + } + + surname = fields.Char(compute='_compute_surname') + + @api.one + @api.depends('name') + def _compute_surname(self): + self.surname = self.name or '' + + +# We want to _inherits from the parent model and we add some fields +# in the child object +class Box(models.Model): + _name = 'test.box' + _inherits = {'test.unit': 'unit_id'} + + unit_id = fields.Many2one('test.unit', 'Unit', required=True, + ondelete='cascade') + field_in_box = fields.Char('Field1') + + +# We add a third level of _inherits +class Pallet(models.Model): + _name = 'test.pallet' + _inherits = {'test.box': 'box_id'} + + box_id = fields.Many2one('test.box', 'Box', required=True, + ondelete='cascade') + field_in_pallet = fields.Char('Field2') diff --git a/openerp/addons/test_inherits/tests/__init__.py b/openerp/addons/test_inherits/tests/__init__.py new file mode 100644 index 00000000000..694d1720d65 --- /dev/null +++ b/openerp/addons/test_inherits/tests/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import test_inherits diff --git a/openerp/addons/test_inherits/tests/test_inherits.py b/openerp/addons/test_inherits/tests/test_inherits.py new file mode 100644 index 00000000000..1fcdbb2f3ec --- /dev/null +++ b/openerp/addons/test_inherits/tests/test_inherits.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +from openerp.tests import common + + +class test_inherits(common.TransactionCase): + + def test_create_3_levels_inherits(self): + """ Check that we can create an inherits on 3 levels """ + pallet = self.env['test.pallet'].create({ + 'name': 'B', + 'field_in_box': 'box', + 'field_in_pallet': 'pallet', + }) + self.assertTrue(pallet) + self.assertEqual(pallet.name, 'B') + self.assertEqual(pallet.field_in_box, 'box') + self.assertEqual(pallet.field_in_pallet, 'pallet') From 461c93081d54db2fa08a166837140ae8e59f3d8f Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Fri, 20 Feb 2015 11:25:57 +0100 Subject: [PATCH 02/19] Add a failing test for write as well --- openerp/addons/test_inherits/tests/test_inherits.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openerp/addons/test_inherits/tests/test_inherits.py b/openerp/addons/test_inherits/tests/test_inherits.py index 1fcdbb2f3ec..7ff5dee814b 100644 --- a/openerp/addons/test_inherits/tests/test_inherits.py +++ b/openerp/addons/test_inherits/tests/test_inherits.py @@ -15,3 +15,9 @@ class test_inherits(common.TransactionCase): self.assertEqual(pallet.name, 'B') self.assertEqual(pallet.field_in_box, 'box') self.assertEqual(pallet.field_in_pallet, 'pallet') + + def test_write_3_levels_inherits(self): + """ Check that we can create an inherits on 3 levels """ + pallet = self.env.ref('test_inherits.pallet_a') + pallet.write({'name': 'C'}) + self.assertEqual(pallet.name, 'C') From 608e58cce13e51d40e6b741dd8ea336fc9cfcb5a Mon Sep 17 00:00:00 2001 From: Guewen Baconnier Date: Tue, 24 Feb 2015 10:34:09 +0100 Subject: [PATCH 03/19] Reload fields of parent inherits, fixes #5398 --- openerp/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openerp/models.py b/openerp/models.py index 3a8cb88423e..8f0ac390d3e 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -2896,6 +2896,7 @@ class BaseModel(object): cls._inherit_fields = struct = {} for parent_model, parent_field in cls._inherits.iteritems(): parent = cls.pool[parent_model] + parent._inherits_reload() for name, column in parent._columns.iteritems(): struct[name] = (parent_model, parent_field, column, parent_model) for name, source in parent._inherit_fields.iteritems(): From 22606c89057cb70ecf8f379144092319bc7275e2 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Tue, 24 Feb 2015 17:33:35 +0100 Subject: [PATCH 04/19] [FIX] report: abstract reports default lang In ir_ui_view.py, in method render (line 132 atm), the values passed to the rendering engine is a merge of the context and the values. Therefore, if at this place, the language is rightly set in the context, the report lang will be as well in the values. In abstract_report.py, the values passed to the render method is the wrapped report localcontext in which are added some key/values (docs, doc_ids, doc_model). By default, the lang in the localcontext is False See __init__ method of rml_parse class in report_sxw.py. If setLang method is not called, the lang in the localcontext remains False. In this rev., we avoid to overwrite the lang from the context by the lang of the localcontext if this one is False, so the lang of the report is set with the current context lang. Forcing the lang of the report to False had as side-effect to prevent the editing of report using the website editor(e.g. playslip_report) opw-628720 --- addons/report/models/abstract_report.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/report/models/abstract_report.py b/addons/report/models/abstract_report.py index 08d70e83e20..cccaa339b63 100644 --- a/addons/report/models/abstract_report.py +++ b/addons/report/models/abstract_report.py @@ -54,7 +54,9 @@ class AbstractReport(osv.AbstractModel): # Rendering self._template with the wrapped report instance localcontext as # rendering environment - docargs = wrapped_report.localcontext + docargs = dict(wrapped_report.localcontext) + if not docargs.get('lang'): + docargs.pop('lang', False) docargs['docs'] = docargs.get('objects') # Used in template translation (see translate_doc method from report model) From 3050e35d31d30a0329dd2abdeb0875edcfdbb9a4 Mon Sep 17 00:00:00 2001 From: Jeremy Kersten Date: Tue, 24 Feb 2015 18:18:25 +0100 Subject: [PATCH 05/19] [FIX] google_calendar: don't send mail from calendar when date has been changed. If a meeting has been changed in google, a mail has been already sent. --- addons/google_calendar/google_calendar.py | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/google_calendar/google_calendar.py b/addons/google_calendar/google_calendar.py index 1e40b708b58..14847b5402d 100644 --- a/addons/google_calendar/google_calendar.py +++ b/addons/google_calendar/google_calendar.py @@ -502,6 +502,7 @@ class google_calendar(osv.AbstractModel): rrule = [rule for rule in single_event_dict["recurrence"] if rule.startswith("RRULE:")][0][6:] result['rrule'] = rrule + context = dict(context or {}, no_mail_to_attendees=True) if type == "write": res = calendar_event.write(cr, uid, event['id'], result, context=context) elif type == "copy": From cb6e27c992e514083b751f58b74edd20bd4f58a0 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Fri, 20 Feb 2015 16:31:36 +0100 Subject: [PATCH 06/19] [IMP] When invoicing from multiple pickings, services should be checked for all purchases/sales [FIX] Should make sure purchase order lines are not invoiced twice --- addons/purchase/stock.py | 26 ++++++++++++++------------ addons/sale_stock/sale_stock.py | 14 +++++++------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/addons/purchase/stock.py b/addons/purchase/stock.py index cf7b445fbf6..2b415f0115f 100644 --- a/addons/purchase/stock.py +++ b/addons/purchase/stock.py @@ -69,6 +69,20 @@ class stock_move(osv.osv): self.pool.get('purchase.order').write(cr, uid, [purchase_line.order_id.id], { 'invoice_ids': [(4, invoice_line_vals['invoice_id'])], }) + purchase_line_obj = self.pool.get('purchase.order.line') + purchase_obj = self.pool.get('purchase.order') + invoice_line_obj = self.pool.get('account.invoice.line') + purchase_id = move.purchase_line_id.order_id.id + purchase_line_ids = purchase_line_obj.search(cr, uid, [('order_id', '=', purchase_id), ('product_id.type', '=', 'service'), ('invoice_lines', '=', False)], context=context) + if purchase_line_ids: + inv_lines = [] + for po_line in purchase_line_obj.browse(cr, uid, purchase_line_ids, context=context): + acc_id = purchase_obj._choose_account_from_po_line(cr, uid, po_line, context=context) + inv_line_data = purchase_obj._prepare_inv_line(cr, uid, acc_id, po_line, context=context) + inv_line_id = invoice_line_obj.create(cr, uid, inv_line_data, context=context) + inv_lines.append(inv_line_id) + po_line.write({'invoice_lines': [(4, inv_line_id)]}) + invoice_line_obj.write(cr, uid, inv_lines, {'invoice_id': invoice_line_vals['invoice_id']}, context=context) return invoice_line_id def _get_master_data(self, cr, uid, move, company, context=None): @@ -148,18 +162,6 @@ class stock_picking(osv.osv): purchase_line_obj = self.pool.get('purchase.order.line') invoice_line_obj = self.pool.get('account.invoice.line') invoice_id = super(stock_picking, self)._create_invoice_from_picking(cr, uid, picking, vals, context=context) - if picking.move_lines and picking.move_lines[0].purchase_line_id: - purchase_id = picking.move_lines[0].purchase_line_id.order_id.id - purchase_line_ids = purchase_line_obj.search(cr, uid, [('order_id', '=', purchase_id), ('product_id.type', '=', 'service'), ('invoiced', '=', False)], context=context) - if purchase_line_ids: - inv_lines = [] - for po_line in purchase_line_obj.browse(cr, uid, purchase_line_ids, context=context): - acc_id = purchase_obj._choose_account_from_po_line(cr, uid, po_line, context=context) - inv_line_data = purchase_obj._prepare_inv_line(cr, uid, acc_id, po_line, context=context) - inv_line_id = invoice_line_obj.create(cr, uid, inv_line_data, context=context) - inv_lines.append(inv_line_id) - po_line.write({'invoice_lines': [(4, inv_line_id)]}) - invoice_line_obj.write(cr, uid, inv_lines, {'invoice_id': invoice_id}, context=context) return invoice_id def _get_invoice_vals(self, cr, uid, key, inv_type, journal_id, move, context=None): diff --git a/addons/sale_stock/sale_stock.py b/addons/sale_stock/sale_stock.py index 66e01cd99a5..b6250abdfe8 100644 --- a/addons/sale_stock/sale_stock.py +++ b/addons/sale_stock/sale_stock.py @@ -368,6 +368,13 @@ class stock_move(osv.osv): self.pool.get('sale.order').write(cr, uid, [sale_line.order_id.id], { 'invoice_ids': [(4, invoice_line_vals['invoice_id'])], }) + sale_line_obj = self.pool.get('sale.order.line') + invoice_line_obj = self.pool.get('account.invoice.line') + sale_line_ids = sale_line_obj.search(cr, uid, [('order_id', '=', move.procurement_id.sale_line_id.order_id.id), ('product_id.type', '=', 'service'), ('invoiced', '=', False)], context=context) + if sale_line_ids: + created_lines = sale_line_obj.invoice_line_create(cr, uid, sale_line_ids, context=context) + invoice_line_obj.write(cr, uid, created_lines, {'invoice_id': invoice_line_vals['invoice_id']}, context=context) + return invoice_line_id def _get_master_data(self, cr, uid, move, company, context=None): @@ -436,13 +443,6 @@ class stock_picking(osv.osv): sale_line_obj = self.pool.get('sale.order.line') invoice_line_obj = self.pool.get('account.invoice.line') invoice_id = super(stock_picking, self)._create_invoice_from_picking(cr, uid, picking, vals, context=context) - if picking.group_id: - sale_ids = sale_obj.search(cr, uid, [('procurement_group_id', '=', picking.group_id.id)], context=context) - if sale_ids: - sale_line_ids = sale_line_obj.search(cr, uid, [('order_id', 'in', sale_ids), ('product_id.type', '=', 'service'), ('invoiced', '=', False)], context=context) - if sale_line_ids: - created_lines = sale_line_obj.invoice_line_create(cr, uid, sale_line_ids, context=context) - invoice_line_obj.write(cr, uid, created_lines, {'invoice_id': invoice_id}, context=context) return invoice_id def _get_invoice_vals(self, cr, uid, key, inv_type, journal_id, move, context=None): From d416492259f08cff03004e8679bf63afde260ab2 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Wed, 18 Feb 2015 18:24:03 +0100 Subject: [PATCH 07/19] [IMP] To determine the invoice we put extra moves in, it is better to take the values from the sale order / purchase order if we can find it through the picking [IMP] Add comments [IMP] Simplify code --- addons/purchase/stock.py | 8 +++++++- addons/sale_stock/sale_stock.py | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/addons/purchase/stock.py b/addons/purchase/stock.py index 2b415f0115f..c4d3ffe4b65 100644 --- a/addons/purchase/stock.py +++ b/addons/purchase/stock.py @@ -89,7 +89,13 @@ class stock_move(osv.osv): if move.purchase_line_id: purchase_order = move.purchase_line_id.order_id return purchase_order.partner_id, purchase_order.create_uid.id, purchase_order.currency_id.id - else: + elif move.picking_id: + # In case of an extra move, it is better to use the data from the original moves + for purchase_move in move.picking_id.move_lines: + if purchase_move.purchase_line_id: + purchase_order = purchase_move.purchase_line_id.order_id + return purchase_order.partner_id, purchase_order.create_uid.id, purchase_order.currency_id.id + partner = move.picking_id and move.picking_id.partner_id or False code = self.get_code_from_locs(cr, uid, move, context=context) if partner and partner.property_product_pricelist_purchase and code == 'incoming': diff --git a/addons/sale_stock/sale_stock.py b/addons/sale_stock/sale_stock.py index b6250abdfe8..cbac47c8e59 100644 --- a/addons/sale_stock/sale_stock.py +++ b/addons/sale_stock/sale_stock.py @@ -381,6 +381,10 @@ class stock_move(osv.osv): if move.procurement_id and move.procurement_id.sale_line_id and move.procurement_id.sale_line_id.order_id.order_policy == 'picking': sale_order = move.procurement_id.sale_line_id.order_id return sale_order.partner_invoice_id, sale_order.user_id.id, sale_order.pricelist_id.currency_id.id + elif move.picking_id.sale_id: + # In case of extra move, it is better to use the same data as the original moves + sale_order = move.picking_id.sale_id + return sale_order.partner_invoice_id, sale_order.user_id.id, sale_order.pricelist_id.currency_id.id return super(stock_move, self)._get_master_data(cr, uid, move, company, context=context) def _get_invoice_line_vals(self, cr, uid, move, partner, inv_type, context=None): From 37f945961071c6905e5b4aadb629c2f1255018b8 Mon Sep 17 00:00:00 2001 From: Josse Colpaert Date: Tue, 17 Feb 2015 11:43:49 +0100 Subject: [PATCH 08/19] [IMP] Return of return should be linked with original destination move should fix #5253 [FIX] Split of return move should keep the returned move [IMP] Better comment and check for state not cancelled --- addons/stock/stock.py | 1 + addons/stock/wizard/stock_return_picking.py | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 9c1bb5dd55b..7a694f7cc32 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -2514,6 +2514,7 @@ class stock_move(osv.osv): 'split_from': move.id, 'procurement_id': move.procurement_id.id, 'move_dest_id': move.move_dest_id.id, + 'origin_returned_move_id': move.origin_returned_move_id.id, } if context.get('source_location_id'): defaults['location_id'] = context['source_location_id'] diff --git a/addons/stock/wizard/stock_return_picking.py b/addons/stock/wizard/stock_return_picking.py index deb624ea797..29b83d9200f 100644 --- a/addons/stock/wizard/stock_return_picking.py +++ b/addons/stock/wizard/stock_return_picking.py @@ -132,6 +132,12 @@ class stock_return_picking(osv.osv_memory): raise osv.except_osv(_('Warning !'), _("You have manually created product lines, please delete them to proceed")) new_qty = data_get.quantity if new_qty: + # The return of a return should be linked with the original's destination move if it was not cancelled + if move.origin_returned_move_id.move_dest_id.id and move.origin_returned_move_id.move_dest_id.state != 'cancel': + move_dest_id = move.origin_returned_move_id.move_dest_id.id + else: + move_dest_id = False + returned_lines += 1 move_obj.copy(cr, uid, move.id, { 'product_id': data_get.product_id.id, @@ -144,6 +150,7 @@ class stock_return_picking(osv.osv_memory): 'origin_returned_move_id': move.id, 'procure_method': 'make_to_stock', 'restrict_lot_id': data_get.lot_id.id, + 'move_dest_id': move_dest_id, }) if not returned_lines: From 22acc5d379ecec68f2c5148096aaa274f1bd6097 Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Wed, 25 Feb 2015 18:26:04 +0100 Subject: [PATCH 09/19] [FIX] project_timesheet: retrocompatibility for 73f7a2ba35e21d60ef98fe8ad5f2a9d735afe196 It looks like it was possible to pass vals['date'] in date format (!= datetime format) to _create_analytic_entries. This rev. is a retrocompatible patch for 73f7a2ba35e21d60ef98fe8ad5f2a9d735afe196. In addition, it solves the same issue than the rev. 73f7a2ba35e21d60ef98fe8ad5f2a9d735afe196, but in the case the project is set on the task after the work hours are created. See ab5ecef476b82c7e00dbd1868de19832ac044f15 opw-628729 --- addons/project_timesheet/project_timesheet.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/addons/project_timesheet/project_timesheet.py b/addons/project_timesheet/project_timesheet.py index 2b04a1a4de3..cefb1e0ef40 100644 --- a/addons/project_timesheet/project_timesheet.py +++ b/addons/project_timesheet/project_timesheet.py @@ -118,9 +118,12 @@ class project_work(osv.osv): vals_line['user_id'] = vals['user_id'] vals_line['product_id'] = result['product_id'] if vals.get('date'): - timestamp = datetime.datetime.strptime(vals['date'], tools.DEFAULT_SERVER_DATETIME_FORMAT) - ts = fields.datetime.context_timestamp(cr, uid, timestamp, context) - vals_line['date'] = ts.strftime(tools.DEFAULT_SERVER_DATE_FORMAT) + if len(vals['date']) > 10: + timestamp = datetime.datetime.strptime(vals['date'], tools.DEFAULT_SERVER_DATETIME_FORMAT) + ts = fields.datetime.context_timestamp(cr, uid, timestamp, context) + vals_line['date'] = ts.strftime(tools.DEFAULT_SERVER_DATE_FORMAT) + else: + vals_line['date'] = vals['date'] # Calculate quantity based on employee's product's uom vals_line['unit_amount'] = vals['hours'] @@ -263,7 +266,7 @@ class task(osv.osv): missing_analytic_entries[task_work.id] = { 'name' : task_work.name, 'user_id' : task_work.user_id.id, - 'date' : task_work.date and task_work.date[:10] or False, + 'date' : task_work.date, 'account_id': acc_id, 'hours' : task_work.hours, 'task_id' : task_obj.id From e9b391c3080b8f0d12d0e7a7a36dc29f8d5a90d3 Mon Sep 17 00:00:00 2001 From: Fabrice Henrion Date: Thu, 26 Feb 2015 00:30:20 -0800 Subject: [PATCH 10/19] [FIX] base_import: typos closes #5478 --- addons/base_import/static/src/xml/import.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/base_import/static/src/xml/import.xml b/addons/base_import/static/src/xml/import.xml index 1f8bd6705a8..0f17765e5b2 100644 --- a/addons/base_import/static/src/xml/import.xml +++ b/addons/base_import/static/src/xml/import.xml @@ -209,9 +209,9 @@ (e.g. a customer that has multiple tags)?

The tags should be separated by a comma without any - spacing. For example, if you want you customer to be - lined to both tags 'Manufacturer' and 'Retailer' - then you will encode it as follow "Manufacturer, + spacing. For example, if you want your customer to be + linked to both tags 'Manufacturer' and 'Retailer' + then you will encode "Manufacturer, Retailer" in the same column of your CSV file.

CSV file for Manufacturer, Retailer
From 10a1d443891001b1ba2094600e8635b59901f33d Mon Sep 17 00:00:00 2001 From: Vijay Baladaniya Date: Thu, 26 Feb 2015 11:17:06 +0530 Subject: [PATCH 11/19] [FIX] Change the target_version for anonymization. Fixed #5476 --- addons/anonymization/anonymization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/anonymization/anonymization.py b/addons/anonymization/anonymization.py index a6d14ec2e05..92a60469ac3 100644 --- a/addons/anonymization/anonymization.py +++ b/addons/anonymization/anonymization.py @@ -538,7 +538,7 @@ class ir_model_fields_anonymize_wizard(osv.osv_memory): data = pickle.loads(base64.decodestring(wizard.file_import)) migration_fix_obj = self.pool.get('ir.model.fields.anonymization.migration.fix') - fix_ids = migration_fix_obj.search(cr, uid, [('target_version', '=', '7.0')]) + fix_ids = migration_fix_obj.search(cr, uid, [('target_version', '=', '8.0')]) fixes = migration_fix_obj.read(cr, uid, fix_ids, ['model_name', 'field_name', 'query', 'query_type', 'sequence']) fixes = group(fixes, ('model_name', 'field_name')) From e700805a43916cc9a90a6a287990e95060a9f84c Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 26 Feb 2015 11:28:39 +0100 Subject: [PATCH 12/19] [FIX] ws doc: language switcher in some iOS versions --- doc/_themes/odoodoc/static/app.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/_themes/odoodoc/static/app.js b/doc/_themes/odoodoc/static/app.js index 64a77c15c40..16f5445d860 100644 --- a/doc/_themes/odoodoc/static/app.js +++ b/doc/_themes/odoodoc/static/app.js @@ -106,8 +106,10 @@ $(function () { // programming language switcher function findSheet(pattern, fromSheet) { if (fromSheet) { - for(var i=0; i Date: Wed, 25 Feb 2015 17:46:38 +0100 Subject: [PATCH 13/19] [FIX] product.template: name_search compatibility with product.product In the rare places with a m2o field to product.template (e.g BoMs), it is necessary to be able to search on product codes, EAN, etc, the way it works on product.product. This is done by delegating the name_search to product.product and then returning the corresponding templates. This has a small penalty of executing name_get() twice, but is simpler and more robust for future changes. An alternative would have been to extract the name_search() method into a mixin and mix it on both product.product and product.template. However this would be more brittle and only work as long as the name_search implementation strictly uses fields that are present in both tables. OPW 626662 --- addons/product/product.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/addons/product/product.py b/addons/product/product.py index 5e0abcc691a..95e21eac7a7 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -22,8 +22,10 @@ import math import re import time +from collections import OrderedDict from _common import ceiling + from openerp import SUPERUSER_ID from openerp import tools from openerp.osv import osv, fields, expression @@ -797,9 +799,16 @@ class product_template(osv.osv): pass return super(product_template, self).name_get(cr, user, ids, context) - - - + def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100): + product_product = self.pool['product.product'] + results = product_product.name_search( + cr, user, name, args, operator=operator, context=context, limit=limit) + product_ids = [p[0] for p in results] + template_ids = [p.product_tmpl_id.id + for p in product_product.browse( + cr, user, product_ids, context=context)] + uniq_ids = OrderedDict.fromkeys(template_ids).keys() + return self.name_get(cr, user, uniq_ids, context=context) class product_product(osv.osv): _name = "product.product" From 05bbe78d87eb143b52a54e5be50faa10cc5c7d24 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 25 Feb 2015 17:52:03 +0100 Subject: [PATCH 14/19] [FIX] product.product: name_search() must preserve natural order The natural order was discarded when merging results with a set, and this was actually un-necessary as the second search() excludes the previously found `ids`. --- addons/product/product.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/addons/product/product.py b/addons/product/product.py index 95e21eac7a7..ad53e5dc3e0 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -1084,12 +1084,11 @@ class product_product(osv.osv): # on a database with thousands of matching products, due to the huge merge+unique needed for the # OR operator (and given the fact that the 'name' lookup results come from the ir.translation table # Performing a quick memory merge of ids in Python will give much better performance - ids = set(self.search(cr, user, args + [('default_code', operator, name)], limit=limit, context=context)) + ids = self.search(cr, user, args + [('default_code', operator, name)], limit=limit, context=context) if not limit or len(ids) < limit: # we may underrun the limit because of dupes in the results, that's fine limit2 = (limit - len(ids)) if limit else False - ids.update(self.search(cr, user, args + [('name', operator, name), ('id', 'not in', list(ids))], limit=limit2, context=context)) - ids = list(ids) + ids += self.search(cr, user, args + [('name', operator, name), ('id', 'not in', ids)], limit=limit2, context=context) elif not ids and operator in expression.NEGATIVE_TERM_OPERATORS: ids = self.search(cr, user, args + ['&', ('default_code', operator, name), ('name', operator, name)], limit=limit, context=context) if not ids and operator in positive_operators: From f0dc416871a40b49981c2ed617fbe8abc10a1d8f Mon Sep 17 00:00:00 2001 From: Christophe Simonis Date: Thu, 26 Feb 2015 14:03:02 +0100 Subject: [PATCH 15/19] [FIX] phantomjs 2.0 compatibility phantomjs 2.0 remove the deprecated attribute `phantom.args`. Use the new `system` module. --- addons/web_tests/tests/test_ui_load.js | 3 ++- openerp/tests/phantomtest.js | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/addons/web_tests/tests/test_ui_load.js b/addons/web_tests/tests/test_ui_load.js index 9d76250d239..8daf8e876e0 100644 --- a/addons/web_tests/tests/test_ui_load.js +++ b/addons/web_tests/tests/test_ui_load.js @@ -1,4 +1,5 @@ // Load helper -phantom.injectJs(phantom.args[0]); +var system = require('system'); +phantom.injectJs(system.args[1]); pt = new PhantomTest(); pt.run("/", "console.log('ok')", "console"); diff --git a/openerp/tests/phantomtest.js b/openerp/tests/phantomtest.js index 49da52e07d9..d37d1ea0fab 100644 --- a/openerp/tests/phantomtest.js +++ b/openerp/tests/phantomtest.js @@ -1,6 +1,8 @@ // Phantomjs odoo helper // jshint evil: true, loopfunc: true +var system = require('system'); + function waitFor (condition, callback, timeout, timeoutMessageCallback) { timeout = timeout || 10000; var start = new Date(); @@ -19,7 +21,7 @@ function waitFor (condition, callback, timeout, timeoutMessageCallback) { function PhantomTest() { var self = this; - this.options = JSON.parse(phantom.args[phantom.args.length-1]); + this.options = JSON.parse(system.args[system.args.length-1]); this.inject = this.options.inject || []; this.timeout = this.options.timeout ? Math.round(parseFloat(this.options.timeout)*1000 - 5000) : 10000; this.origin = 'http://localhost'; @@ -143,7 +145,7 @@ function PhantomTest() { } // js mode or jsfile mode -if(phantom.args.length === 1) { +if(system.args.length === 2) { pt = new PhantomTest(); pt.run(pt.options.url_path, pt.options.code, pt.options.ready); } From 51f61ed23d1fcc311900f45f6e7b88c53f51db93 Mon Sep 17 00:00:00 2001 From: Daniel Kraft Date: Mon, 23 Feb 2015 19:03:31 +0100 Subject: [PATCH 16/19] [ADD] D9T GmbH CLA closes #5433 --- doc/cla/corporate/d9t.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 doc/cla/corporate/d9t.md diff --git a/doc/cla/corporate/d9t.md b/doc/cla/corporate/d9t.md new file mode 100644 index 00000000000..3c20e3fa4a4 --- /dev/null +++ b/doc/cla/corporate/d9t.md @@ -0,0 +1,16 @@ +Germany, 2015-02-23 + +D9T GmbH agrees to the terms of the Odoo Corporate Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Daniel Kraft, daniel.kraft@d9t.de https://github.com/wamdam + +List of contributors: + +Daniel Kraft, daniel.kraft@d9t.de https://github.com/wamdam +Oliver Roch, oliver.roch@d9t.de https://github.com/veroc From 5f45e7ca96f34fa8e904cdaa46576b60ffcfb477 Mon Sep 17 00:00:00 2001 From: David Monjoie Date: Thu, 26 Feb 2015 15:42:01 +0100 Subject: [PATCH 17/19] [FIX] hr_timesheet_sheet: properly unlink timesheets when deleting a timesheet_sheet Adaptation of https://github.com/acsone/odoo/commit/afe4a97d037b4075c8cd0514b6d1b829e1208629, but we properly unlink the timesheet lines themselves instead of going for the account.analytic.lines like the original commit proposition did. Actually, the unlink method of hr.analytic.timesheet already unlinks the corresponding acount.analytic.line, but those records were previously deleted via ondelete='cascade', which didn't trigger the unlink method at all, so the associated analytic lines were not deleted accordingly. Fixes opw 628256. --- addons/hr_timesheet_sheet/hr_timesheet_sheet.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py index f59bceb9a82..d2d0d0a0533 100644 --- a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py +++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py @@ -256,6 +256,14 @@ class hr_timesheet_sheet(osv.osv): raise osv.except_osv(_('Invalid Action!'), _('You cannot delete a timesheet which is already confirmed.')) elif sheet['total_attendance'] <> 0.00: raise osv.except_osv(_('Invalid Action!'), _('You cannot delete a timesheet which have attendance entries.')) + + toremove = [] + analytic_timesheet = self.pool.get('hr.analytic.timesheet') + for sheet in self.browse(cr, uid, ids, context=context): + for timesheet in sheet.timesheet_ids: + toremove.append(timesheet.id) + analytic_timesheet.unlink(cr, uid, toremove, context=context) + return super(hr_timesheet_sheet, self).unlink(cr, uid, ids, context=context) def onchange_employee_id(self, cr, uid, ids, employee_id, context=None): From 25bf2674d542129d7689640fe824428fb618f65b Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Thu, 26 Feb 2015 17:19:04 +0100 Subject: [PATCH 18/19] [FIX] web: prevent set value of priority if readonly The priority widget did not handle the readonly concept It was therefore possible to set the priority of an issue (for instance) even if the field was marked as readonly. opw-628960 --- addons/web/static/src/js/view_form.js | 4 +++- addons/web/static/src/xml/base.xml | 3 ++- addons/web_kanban/static/src/js/kanban.js | 8 +++++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index f2802c3f5f9..df1b0033569 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -2424,7 +2424,9 @@ instance.web.form.Priority = instance.web.form.FieldChar.extend({ this.record_id = this.view.datarecord.id; this.priorities = this.prepare_priority(); this.$el.html(QWeb.render("Priority", {'widget': this})); - this.$el.find('li').on('click', this.set_priority.bind(this)); + if (!this.get('readonly')){ + this.$el.find('li').on('click', this.set_priority.bind(this)); + } }, /* setting the value: in view mode, perform an asynchronous call and reload the form view; in edit mode, use set_value to save the new value that will diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 864bc899960..145d9a02d95 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -1056,9 +1056,10 @@ diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index cd92aadf303..fc9c53ed836 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -1300,8 +1300,14 @@ instance.web_kanban.Priority = instance.web_kanban.AbstractField.extend({ var self = this; this.record_id = self.parent.id; this.priorities = self.prepare_priority(); + var readonly = this.field && this.field.readonly; + if (readonly){ + this.set('readonly', true); + } this.$el = $(QWeb.render("Priority", {'widget': this})); - this.$el.find('li').click(self.do_action.bind(self)); + if (!readonly){ + this.$el.find('li').click(self.do_action.bind(self)); + } }, do_action: function(e) { var self = this; From d4f41a18a6a21ea627f2cabccb17d4049f4ad0cc Mon Sep 17 00:00:00 2001 From: Denis Ledoux Date: Thu, 26 Feb 2015 17:38:39 +0100 Subject: [PATCH 19/19] [FIX] sale: sales count button on variants The button was no more redirecting to the sales order lines list view, following a distraction error introduced during 68b8286d5fe384af2ce05b58807e532ffa27a796 --- addons/sale/sale_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml index cdcdf275e1e..6afdb967414 100644 --- a/addons/sale/sale_view.xml +++ b/addons/sale/sale_view.xml @@ -503,7 +503,7 @@