diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index dcc272ad1f2..ded848e3635 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -101,7 +101,7 @@ class AccountBankStatement(models.Model): journals = self.env['account.journal'].search([('type', '=', journal_type), ('company_id', '=', company_id)]) if journals: return journals[0] - return False + return self.env['account.journal'] @api.multi def _get_opening_balance(self, journal_id): diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 74c3d2ca30a..ab6f1c65e18 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -508,7 +508,7 @@ class AccountInvoice(models.Model): report_invoice = self.env['report']._get_report_from_name('account.report_invoice') except IndexError: report_invoice = False - if report_invoice: + if report_invoice and report_invoice.attachment: for invoice in self: with invoice.env.do_in_draft(): invoice.number, invoice.state = invoice.move_name, 'open' diff --git a/addons/l10n_be_intrastat/wizard/xml_decl.py b/addons/l10n_be_intrastat/wizard/xml_decl.py index 2771e8b63cd..35151da426d 100644 --- a/addons/l10n_be_intrastat/wizard/xml_decl.py +++ b/addons/l10n_be_intrastat/wizard/xml_decl.py @@ -316,8 +316,10 @@ class xml_decl(osv.TransientModel): numlgn = 0 for linekey in entries: - numlgn += 1 amounts = entries[linekey] + if round(amounts[0], 0) == 0: + continue + numlgn += 1 item = ET.SubElement(datas, 'Item') self._set_Dim(item, 'EXSEQCODE', unicode(numlgn)) self._set_Dim(item, 'EXTRF', unicode(linekey.EXTRF)) diff --git a/addons/payment_paypal/models/res_company.py b/addons/payment_paypal/models/res_company.py index 83ac270be0c..adbb328d562 100644 --- a/addons/payment_paypal/models/res_company.py +++ b/addons/payment_paypal/models/res_company.py @@ -10,7 +10,6 @@ class ResCompany(osv.Model): Acquirer = self.pool['payment.acquirer'] company_id = self.pool['res.users'].browse(cr, uid, uid, context=context).company_id.id paypal_ids = Acquirer.search(cr, uid, [ - ('website_published', '=', True), ('name', 'ilike', 'paypal'), ('company_id', '=', company_id), ], limit=1, context=context) @@ -24,7 +23,6 @@ class ResCompany(osv.Model): company_id = self.pool['res.users'].browse(cr, uid, uid, context=context).company_id.id paypal_account = self.browse(cr, uid, id, context=context).paypal_account paypal_ids = Acquirer.search(cr, uid, [ - ('website_published', '=', True), ('paypal_email_account', '=', paypal_account), ('company_id', '=', company_id), ], context=context) diff --git a/addons/project/project.py b/addons/project/project.py index 4d2d50ce1b7..be29132112c 100644 --- a/addons/project/project.py +++ b/addons/project/project.py @@ -379,7 +379,10 @@ class task(osv.osv): """ Gives default stage_id """ if context is None: context = {} - return self.stage_find(cr, uid, [], context.get('default_project_id'), [('fold', '=', False)], context=context) + default_project_id = context.get('default_project_id') + if not default_project_id: + return False + return self.stage_find(cr, uid, [], default_project_id, [('fold', '=', False)], context=context) def _read_group_stage_ids(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None): if context is None: @@ -417,11 +420,16 @@ class task(osv.osv): @api.cr_uid_ids_context def onchange_project(self, cr, uid, id, project_id, context=None): + values = {} if project_id: project = self.pool.get('project.project').browse(cr, uid, project_id, context=context) if project and project.partner_id: - return {'value': {'partner_id': project.partner_id.id}} - return {} + values['partner_id'] = project.partner_id.id + values['stage_id'] = self.stage_find(cr, uid, [], project_id, [('fold', '=', False)], context=context) + else: + values['stage_id'] = False + values['partner_id'] = False + return {'value': values} def onchange_user_id(self, cr, uid, ids, user_id, context=None): vals = {} diff --git a/addons/project_issue/project_issue.py b/addons/project_issue/project_issue.py index b0a9dbf0b25..1b323917860 100644 --- a/addons/project_issue/project_issue.py +++ b/addons/project_issue/project_issue.py @@ -33,7 +33,10 @@ class project_issue(osv.Model): """ Gives default stage_id """ if context is None: context = {} - return self.stage_find(cr, uid, [], context.get('default_project_id'), [('fold', '=', False)], context=context) + default_project_id = context.get('default_project_id') + if not default_project_id: + return False + return self.stage_find(cr, uid, [], default_project_id, [('fold', '=', False)], context=context) def _read_group_stage_ids(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None): if context is None: @@ -117,11 +120,18 @@ class project_issue(osv.Model): return res def on_change_project(self, cr, uid, ids, project_id, context=None): + values = {} if project_id: project = self.pool.get('project.project').browse(cr, uid, project_id, context=context) if project and project.partner_id: - return {'value': {'partner_id': project.partner_id.id, 'email_from': project.partner_id.email}} - return {} + values['partner_id'] = project.partner_id.id + values['email_from'] = project.partner_id.email + values['stage_id'] = self.stage_find(cr, uid, [], project_id, [('fold', '=', False)], context=context) + else: + values['partner_id'] = False + values['email_from'] = False + values['stage_id'] = False + return {'value': values} _columns = { 'id': fields.integer('ID', readonly=True), diff --git a/addons/purchase/invoice.py b/addons/purchase/invoice.py index 554c5a7d7da..eb580dd4a6b 100644 --- a/addons/purchase/invoice.py +++ b/addons/purchase/invoice.py @@ -159,13 +159,14 @@ class AccountInvoice(models.Model): #line['tax_ids'] is like [(4, tax_id, None), (4, tax_id2, None)...] taxes = self.env['account.tax'].browse([x[1] for x in line['tax_ids']]) price_unit = taxes.compute_all(price_unit, currency=inv.currency_id, quantity=1.0)['total_excluded'] + price_before = line.get('price', 0.0) line.update({'price': round(valuation_price_unit * line['quantity'], account_prec)}) diff_res.append({ 'type': 'src', 'name': i_line.name[:64], 'price_unit': round(price_unit - valuation_price_unit, account_prec), 'quantity': line['quantity'], - 'price': round((price_unit - valuation_price_unit) * line['quantity'], account_prec), + 'price': round(price_before - line.get('price', 0.0), account_prec), 'account_id': acc, 'product_id': line['product_id'], 'uom_id': line['uom_id'], diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py index 39441246b3f..2cf7ff21f42 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -77,12 +77,15 @@ class PurchaseOrder(models.Model): types = type_obj.search([('code', '=', 'incoming'), ('warehouse_id', '=', False)]) return types[:1] - @api.depends('order_line.move_ids.picking_id') + @api.depends('order_line.move_ids') def _compute_picking(self): for order in self: pickings = self.env['stock.picking'] for line in order.order_line: - moves = line.move_ids.filtered(lambda r: r.state != 'cancel') + # We keep a limited scope on purpose. Ideally, we should also use move_orig_ids and + # do some recursive search, but that could be prohibitive if not done correctly. + moves = line.move_ids | line.move_ids.mapped('returned_move_ids') + moves = moves.filtered(lambda r: r.state != 'cancel') pickings |= moves.mapped('picking_id') order.picking_ids = pickings order.picking_count = len(pickings) diff --git a/addons/sale_service/models/sale_service.py b/addons/sale_service/models/sale_service.py index e6343e940dc..05a9f67bdb1 100644 --- a/addons/sale_service/models/sale_service.py +++ b/addons/sale_service/models/sale_service.py @@ -33,6 +33,16 @@ class procurement_order(osv.osv): def _run(self, cr, uid, procurement, context=None): if self._is_procurement_task(cr, uid, procurement, context=context) and not procurement.task_id: + # If the SO was confirmed, cancelled, set to draft then confirmed, avoid creating a new + # task. + if procurement.sale_line_id: + existing_task = self.pool['project.task'].search( + cr, uid, [('sale_line_id', '=', procurement.sale_line_id.id)], + context=context + ) + if existing_task: + return existing_task + #create a task for the procurement return self._create_service_task(cr, uid, procurement, context=context) return super(procurement_order, self)._run(cr, uid, procurement, context=context) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index b51ee81f169..8291aedc1a4 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -412,10 +412,12 @@ class stock_quant(osv.osv): quants_reconcile = [] to_move_quants = [] self._check_location(cr, uid, location_to, context=context) + check_lot = False for quant, qty in quants: if not quant: #If quant is None, we will create a quant to move (and potentially a negative counterpart too) quant = self._quant_create(cr, uid, qty, move, lot_id=lot_id, owner_id=owner_id, src_package_id=src_package_id, dest_package_id=dest_package_id, force_location_from=location_from, force_location_to=location_to, context=context) + check_lot = True else: self._quant_split(cr, uid, quant, qty, context=context) to_move_quants.append(quant) @@ -434,6 +436,26 @@ class stock_quant(osv.osv): for quant in quants_reconcile: self._quant_reconcile_negative(cr, uid, quant, move, context=context) + # In case of serial tracking, check if the product does not exist somewhere internally already + # Checking that a positive quant already exists in an internal location is too restrictive. + # Indeed, if a warehouse is configured with several steps (e.g. "Pick + Pack + Ship") and + # one step is forced (creates a quant of qty = -1.0), it is not possible afterwards to + # correct the inventory unless the product leaves the stock. + picking_type = move.picking_id and move.picking_id.picking_type_id or False + if check_lot and lot_id and move.product_id.tracking == 'serial' and (not picking_type or (picking_type.use_create_lots or picking_type.use_existing_lots)): + other_quants = self.search(cr, uid, [('product_id', '=', move.product_id.id), ('lot_id', '=', lot_id), + ('location_id.usage', '=', 'internal')], context=context) + + if other_quants: + # We raise an error if: + # - the total quantity is strictly larger than 1.0 + # - there are more than one negative quant, to avoid situations where the user would + # force the quantity at several steps of the process + other_quants = self.browse(cr, uid, other_quants, context=context) + if sum(other_quants.mapped('qty')) > 1.0 or len([q for q in other_quants.mapped('qty') if q < 0]) > 1: + lot_name = self.pool['stock.production.lot'].browse(cr, uid, lot_id, context=context).name + raise UserError(_('The serial number %s is already in stock.') % lot_name + _("Otherwise make sure the right stock/owner is set.")) + def move_quants_write(self, cr, uid, quants, move, location_dest_id, dest_package_id, lot_id = False, entire_pack=False, context=None): context=context or {} vals = {'location_id': location_dest_id.id, @@ -458,7 +480,8 @@ class stock_quant(osv.osv): if ops: restrict_lot_id = lot_id location = ops.location_id - domain += [('owner_id', '=', ops.owner_id.id)] + if ops.owner_id: + domain += [('owner_id', '=', ops.owner_id.id)] if ops.package_id and not ops.product_id: domain += [('package_id', 'child_of', ops.package_id.id)] elif ops.package_id and ops.product_id: @@ -469,7 +492,8 @@ class stock_quant(osv.osv): else: restrict_lot_id = move.restrict_lot_id.id location = move.location_id - domain += [('owner_id', '=', move.restrict_partner_id.id)] + if move.restrict_partner_id: + domain += [('owner_id', '=', move.restrict_partner_id.id)] domain += [('location_id', 'child_of', move.location_id.id)] if context.get('force_company'): domain += [('company_id', '=', context.get('force_company'))] @@ -565,16 +589,10 @@ class stock_quant(osv.osv): negative_quant_id = self.create(cr, SUPERUSER_ID, negative_vals, context=context) vals.update({'propagated_from_id': negative_quant_id}) - # In case of serial tracking, check if the product does not exist somewhere internally already picking_type = move.picking_id and move.picking_id.picking_type_id or False if lot_id and move.product_id.tracking == 'serial' and (not picking_type or (picking_type.use_create_lots or picking_type.use_existing_lots)): if qty != 1.0: raise UserError(_('You should only receive by the piece with the same serial number')) - other_quants = self.search(cr, uid, [('product_id', '=', move.product_id.id), ('lot_id', '=', lot_id), - ('qty', '>', 0.0), ('location_id.usage', '=', 'internal')], context=context) - if other_quants: - lot_name = self.pool['stock.production.lot'].browse(cr, uid, lot_id, context=context).name - raise UserError(_('The serial number %s is already in stock.') % lot_name + _("Otherwise make sure the right stock/owner is set.")) #create the quant as superuser, because we want to restrict the creation of quant manually: we should always use this method to create quants quant_id = self.create(cr, SUPERUSER_ID, vals, context=context) @@ -2461,7 +2479,7 @@ class stock_move(osv.osv): rounding = ops.product_id.uom_id.rounding for pack_lot in ops.pack_lot_ids: lot_qty[pack_lot.lot_id.id] = uom_obj._compute_qty(cr, uid, ops.product_uom_id.id, pack_lot.qty, ops.product_id.uom_id.id) - for record in ops.linked_move_operation_ids: + for record in ops.linked_move_operation_ids.filtered(lambda x: x.move_id.id in main_domain): move_qty = record.qty move = record.move_id domain = main_domain[move.id] diff --git a/addons/stock_dropshipping/__init__.py b/addons/stock_dropshipping/__init__.py index 67dee8c60db..d7156f09070 100644 --- a/addons/stock_dropshipping/__init__.py +++ b/addons/stock_dropshipping/__init__.py @@ -1,2 +1,3 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +import models # noqa diff --git a/addons/stock_dropshipping/models/__init__.py b/addons/stock_dropshipping/models/__init__.py new file mode 100644 index 00000000000..16c708eba5a --- /dev/null +++ b/addons/stock_dropshipping/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import stock_dropshipping # noqa diff --git a/addons/stock_dropshipping/models/stock_dropshipping.py b/addons/stock_dropshipping/models/stock_dropshipping.py new file mode 100644 index 00000000000..0c2e2424fa0 --- /dev/null +++ b/addons/stock_dropshipping/models/stock_dropshipping.py @@ -0,0 +1,19 @@ +# coding: utf-8 +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from openerp import models, api + +class AccountInvoice(models.Model): + _inherit = 'account.invoice' + + @api.model + def _anglo_saxon_sale_move_lines(self, i_line): + for sale_line in i_line.sale_line_ids: + for proc in sale_line.procurement_ids: + if proc.purchase_line_id: + # if the invoice line is related to sale order lines having one of its + # procurement_ids with a purchase_line_id set, it means that it is a + # confirmed dropship and in that case we mustn't create the cost of + # sale line (because the product won't enter the stock) + return [] + return super(AccountInvoice, self)._anglo_saxon_sale_move_lines(i_line) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 674eebdea6a..a07d9750a59 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -640,8 +640,8 @@ class Database(http.Controller): try: # country code could be = "False" which is actually True in python country_code = post.get('country_code') or False - request.session.proxy("db").create_database(master_pwd, name, bool(post.get('demo')), lang, password, post.get('login'), country_code) - request.session.authenticate(name, 'admin', password) + request.session.proxy("db").create_database(master_pwd, name, bool(post.get('demo')), lang, password, post['login'], country_code) + request.session.authenticate(name, post['login'], password) return http.local_redirect('/web/') except Exception, e: error = "Database creation error: %s" % e diff --git a/addons/web/static/src/js/views/form_widgets.js b/addons/web/static/src/js/views/form_widgets.js index 8f046859ee8..d4128c0f733 100644 --- a/addons/web/static/src/js/views/form_widgets.js +++ b/addons/web/static/src/js/views/form_widgets.js @@ -1565,7 +1565,7 @@ var FieldStatus = common.AbstractField.extend({ }); } } - }, 300), + }, 300, true), }); var FieldMonetary = FieldFloat.extend({ diff --git a/addons/web/static/src/js/views/pivot_view.js b/addons/web/static/src/js/views/pivot_view.js index 8b41efbc3dc..c1752cbe10e 100644 --- a/addons/web/static/src/js/views/pivot_view.js +++ b/addons/web/static/src/js/views/pivot_view.js @@ -679,7 +679,6 @@ var PivotView = View.extend({ if (width > 1) { var total_cell = {width:nbr_measures, height: depth, title:""}; if (nbr_measures === 1) { - total_cell.title = this.measures[this.active_measures[0]].string; total_cell.total = true; } result[0].push(total_cell); diff --git a/addons/web/views/database_manager.html b/addons/web/views/database_manager.html index 7bbed8f3c20..65c368ea39a 100644 --- a/addons/web/views/database_manager.html +++ b/addons/web/views/database_manager.html @@ -69,7 +69,7 @@
- +
diff --git a/addons/web_tip/static/src/js/tip.js b/addons/web_tip/static/src/js/tip.js index 85774c1937b..47d20a2f7ee 100644 --- a/addons/web_tip/static/src/js/tip.js +++ b/addons/web_tip/static/src/js/tip.js @@ -40,7 +40,10 @@ var Tips = Class.extend({ on_view: function() { var self = this; - bus.on('DOM_updated', this, function() { + if (this._DOM_updated) { + bus.off('DOM_updated', this, this._DOM_updated); + } + this._DOM_updated = function() { var action_id = self.view.ViewManager.action ? self.view.ViewManager.action.id : null; var model = self.view.fields_view.model; var mode = self.view.fields_view.type; @@ -49,14 +52,17 @@ var Tips = Class.extend({ self.on_form_view(action_id, model); } else if (self.view.hasOwnProperty('editor')) { - self.view.on('view_list_rendered', self, function() { - self.eval_tips(action_id, model, mode); - }); + if (self._view_list_rendered) { + self.view.off('view_list_rendered', self, self._view_list_rendered); + } + self._view_list_rendered = function() { self.eval_tips(action_id, model, mode); }; + self.view.on('view_list_rendered', self, self._view_list_rendered); } else { self.eval_tips(action_id, model, mode); } - }); + }; + bus.on('DOM_updated', this, this._DOM_updated); }, on_form_view: function(action_id, model) { @@ -64,19 +70,26 @@ var Tips = Class.extend({ var type = this.view.datarecord ? this.view.datarecord.type : null if ($('.oe_chatter').length > 0) { - this.view.on('chatter_messages_displayed', this, function() { - self.eval_tips(action_id, model, 'form', type); - }); + if (this._chatter_messages_displayed) { + this.view.off('chatter_messages_displayed', this, this._chatter_messages_displayed); + } + this._chatter_messages_displayed = function() { self.eval_tips(action_id, model, 'form', type); }; + this.view.on('chatter_messages_displayed', this, this._chatter_messages_displayed); } else { this.eval_tips(action_id, model, 'form', type); } - this.view.on('to_edit_mode', this, function() { - self.remove_tips(); - }); - this.view.on('to_view_mode', this, function() { - self.eval_tips(action_id, model, 'form', type); - }); + if (this._to_edit_mode) { + this.view.off('to_edit_mode', this, this._to_edit_mode); + } + this._to_edit_mode = function() { self.remove_tips(); }; + this.view.on('to_edit_mode', this, this._to_edit_mode); + + if (this._to_view_mode) { + this.view.off('to_view_mode', this, this._to_view_mode); + } + this._to_view_mode = function() { self.eval_tips(action_id, model, 'form', type); }; + this.view.on('to_view_mode', this, this._to_view_mode); }, eval_tips: function(action_id, model, mode, type) { @@ -123,11 +136,12 @@ var Tips = Class.extend({ var Tip = Class.extend({ init: function(tip) { + var self = this; this.tip = tip; - this.highlight_selector = tip.highlight_selector; this.end_selector = tip.end_selector ? tip.end_selector : tip.highlight_selector; this.triggers = tip.trigger_selector ? tip.trigger_selector.split(',') : []; + this._resize = function() { self._set_breathing_position(); }; }, do_tip: function() { @@ -171,17 +185,9 @@ var Tip = Class.extend({ }); // resize - bus.on('resize', this, function() { - self._set_breathing_position(); - }); - - bus.on('image_loaded', this, function() { - self._set_breathing_position(); - }); - - bus.on('please_reposition_tip', this, function() { - self._set_breathing_position(); - }); + bus.on('resize', this, this._resize); + bus.on('image_loaded', this, this._resize); + bus.on('please_reposition_tip', this, this._resize); return true; }, @@ -246,6 +252,7 @@ var Tip = Class.extend({ this.$overlay.on('click', function($ev) { self.end_tip(); }); + $(document).on('keyup.web_tip', function($ev) { if ($ev.which === 27) { // esc self.end_tip(); @@ -253,9 +260,8 @@ var Tip = Class.extend({ }); // resize - bus.on('resize', this, function() { - self._set_popover_position(); - }); + this._resize_position = function() { self._set_popover_position(); }; + bus.on('resize', this, this._resize_position); }, scroll_to_tip: function(){ @@ -278,11 +284,16 @@ var Tip = Class.extend({ this.$overlay.remove(); this.$cross.remove(); - _.each($('.oe_tip_fix_parent'), function(el) { - $(el).removeClass('oe_tip_fix_parent'); + $('.oe_tip_fix_parent').each(function() { + $(this).removeClass('oe_tip_fix_parent'); }); $(document).off('keyup.web_tip'); + bus.off('resize', this, this._resize_position); + bus.off('resize', this, this._resize); + bus.off('image_loaded', this, this._resize); + bus.off('please_reposition_tip', this, this._resize); + Tips.call('consume', [this.tip.id], {}); this.tip.is_consumed = true; }, diff --git a/addons/website/models/website.py b/addons/website/models/website.py index c2bba929f6f..9147f1c0068 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -666,6 +666,9 @@ class res_partner(osv.osv): 'zoom': zoom, 'sensor': 'false', } + google_maps_api_key = self.pool['ir.config_parameter'].get_param(cr, openerp.SUPERUSER_ID, 'google_maps_api_key', context=context) + if google_maps_api_key: + params['key'] = google_maps_api_key return urlplus('//maps.googleapis.com/maps/api/staticmap' , params) def google_map_link(self, cr, uid, ids, zoom=10, context=None): diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index 796ffaca693..95926b08b58 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -908,7 +908,14 @@ Sitemap: sitemap.xml "fields": ["name", "address", "phone", "mobile", "fax", "email"]}'/> - Visualize on Google Maps + + + + + + + Visualize on Google Maps +