diff --git a/addons/anonymization/anonymization.py b/addons/anonymization/anonymization.py index 1134a849f6f..35c6f98d06e 100644 --- a/addons/anonymization/anonymization.py +++ b/addons/anonymization/anonymization.py @@ -539,7 +539,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')) 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
diff --git a/addons/google_calendar/google_calendar.py b/addons/google_calendar/google_calendar.py index 70c6cceaed4..d4743f22bac 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": diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py index e71514f1d32..9ac36bbc63c 100644 --- a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py +++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py @@ -265,6 +265,14 @@ class hr_timesheet_sheet(osv.osv): raise UserError(_('You cannot delete a timesheet which is already confirmed.')) elif sheet['total_attendance'] <> 0.00: raise UserError(_('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): diff --git a/addons/product/product.py b/addons/product/product.py index c2596b93e0c..72a7848fd8c 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 @@ -796,9 +798,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" @@ -1068,12 +1077,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: diff --git a/addons/project_timesheet/project_timesheet.py b/addons/project_timesheet/project_timesheet.py index dfa2ea0cad7..9ec821dfc9c 100644 --- a/addons/project_timesheet/project_timesheet.py +++ b/addons/project_timesheet/project_timesheet.py @@ -115,9 +115,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'] @@ -260,7 +263,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 diff --git a/addons/purchase/stock.py b/addons/purchase/stock.py index 102ef73deea..00c69b4e86b 100644 --- a/addons/purchase/stock.py +++ b/addons/purchase/stock.py @@ -70,13 +70,33 @@ 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): 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': @@ -149,18 +169,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/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) diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml index 246356b42c2..1baa2448c8c 100644 --- a/addons/sale/sale_view.xml +++ b/addons/sale/sale_view.xml @@ -517,7 +517,7 @@ diff --git a/addons/sale_stock/sale_stock.py b/addons/sale_stock/sale_stock.py index 683b2ff47db..60866b782c3 100644 --- a/addons/sale_stock/sale_stock.py +++ b/addons/sale_stock/sale_stock.py @@ -374,12 +374,23 @@ 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): 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): @@ -442,13 +453,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): diff --git a/addons/stock/stock.py b/addons/stock/stock.py index 48c1eaec9bb..fa004c232d0 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -2530,6 +2530,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 c72a4ccce86..24440192dde 100644 --- a/addons/stock/wizard/stock_return_picking.py +++ b/addons/stock/wizard/stock_return_picking.py @@ -133,6 +133,12 @@ class stock_return_picking(osv.osv_memory): raise UserError(_("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, @@ -145,6 +151,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: diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index bed0cefbd9e..ba7a689a8a4 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -2501,7 +2501,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 ab242df110b..bbf83d02b0e 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -1118,9 +1118,10 @@ diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index 6b8843bd0f7..4ad9c893097 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -1332,8 +1332,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; 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/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 + + + 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..7ff5dee814b --- /dev/null +++ b/openerp/addons/test_inherits/tests/test_inherits.py @@ -0,0 +1,23 @@ +# -*- 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') + + 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') diff --git a/openerp/models.py b/openerp/models.py index 8d2aff9df3a..38a1cf85ea4 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -2907,6 +2907,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(): diff --git a/openerp/tests/phantomtest.js b/openerp/tests/phantomtest.js index 8a0f54c97b8..4f264118485 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); }