diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index 09ec5256593..32cbefa051a 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 f7b45c2d037..6f7fee90d01 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -525,7 +525,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/project/project.py b/addons/project/project.py index 7578046c147..35c0a1b6340 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: @@ -409,11 +412,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 7e78dc683e6..2f634ef2c6a 100644 --- a/addons/purchase/invoice.py +++ b/addons/purchase/invoice.py @@ -160,13 +160,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 8d558931ce5..e9ee4877209 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -73,12 +73,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_timesheet/models/sale_service.py b/addons/sale_timesheet/models/sale_service.py index a60ed3a8167..d34b55f4708 100644 --- a/addons/sale_timesheet/models/sale_service.py +++ b/addons/sale_timesheet/models/sale_service.py @@ -29,6 +29,16 @@ class procurement_order(osv.osv): def _run(self, cr, uid, ids, context=None): procurement = self.browse(cr, uid, ids[0], context=context) if procurement._is_procurement_task() 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, ids, context=context) diff --git a/addons/stock/models/stock_quant.py b/addons/stock/models/stock_quant.py index b0d1c2d92c1..dac57f6b321 100644 --- a/addons/stock/models/stock_quant.py +++ b/addons/stock/models/stock_quant.py @@ -177,11 +177,13 @@ class Quant(models.Model): quants_reconcile_sudo = self.env['stock.quant'].sudo() quants_move_sudo = self.env['stock.quant'].sudo() + 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_from_move( 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) + check_lot = True else: quant._quant_split(qty) quants_move_sudo |= quant @@ -201,6 +203,24 @@ class Quant(models.Model): if self._cr.fetchone(): quants_reconcile_sudo._quant_reconcile_negative(move) + # 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([('product_id', '=', move.product_id.id), ('lot_id', '=', lot_id), + ('qty', '>', 0.0), ('location_id.usage', '=', 'internal')]) + 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 + 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.env['stock.production.lot'].browse(lot_id).name + raise UserError(_('The serial number %s is already in stock.') % lot_name + _("Otherwise make sure the right stock/owner is set.")) + @api.model def _quant_create_from_move(self, qty, move, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, @@ -234,16 +254,10 @@ class Quant(models.Model): negative_quant_id = self.sudo().create(negative_vals) vals.update({'propagated_from_id': negative_quant_id.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([('product_id', '=', move.product_id.id), ('lot_id', '=', lot_id), - ('qty', '>', 0.0), ('location_id.usage', '=', 'internal')]) - if other_quants: - lot_name = self.env['stock.production.lot'].browse(lot_id).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 return self.sudo().create(vals) @@ -423,7 +437,9 @@ class Quant(models.Model): if pack_operation_id: pack_operation = self.env['stock.pack.operation'].browse(pack_operation_id) - domain += [('owner_id', '=', pack_operation.owner_id.id), ('location_id', '=', pack_operation.location_id.id)] + domain += [('location_id', '=', pack_operation.location_id.id)] + if pack_operation_id.owner_id: + domain += [('owner_id', '=', pack_operation.owner_id.id)] if pack_operation.package_id and not pack_operation.product_id: domain += [('package_id', 'child_of', pack_operation.package_id.id)] elif pack_operation.package_id and pack_operation.product_id: @@ -431,7 +447,9 @@ class Quant(models.Model): else: domain += [('package_id', '=', False)] else: - domain += [('owner_id', '=', move.restrict_partner_id.id), ('location_id', 'child_of', move.location_id.id)] + domain += [('location_id', 'child_of', move.location_id.id)] + if move.restrict_partner_id: + domain += [('owner_id', '=', move.restrict_partner_id.id)] if company_id: domain += [('company_id', '=', company_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 902e7500650..057bdd45aef 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -638,8 +638,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 3bc97996962..3b4628322f7 100644 --- a/addons/web/static/src/js/views/form_widgets.js +++ b/addons/web/static/src/js/views/form_widgets.js @@ -1440,7 +1440,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 83e75faf9a6..264fa2af985 100644 --- a/addons/web/static/src/js/views/pivot_view.js +++ b/addons/web/static/src/js/views/pivot_view.js @@ -692,7 +692,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 dfcb95d4d05..7cef4678e8d 100644 --- a/addons/web/views/database_manager.html +++ b/addons/web/views/database_manager.html @@ -69,7 +69,7 @@
- +
diff --git a/addons/web_editor/static/src/less/web_editor.ui.components.less b/addons/web_editor/static/src/less/web_editor.ui.components.less index 6cf994e2f03..a21d63fe642 100644 --- a/addons/web_editor/static/src/less/web_editor.ui.components.less +++ b/addons/web_editor/static/src/less/web_editor.ui.components.less @@ -243,7 +243,8 @@ body .modal { font-weight: bold; } .o_bg_img_opt_object { - width: 100%; + display: inline-block; + max-width: 100%; background: white; min-height: 10px; position: relative; 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 2a22bc673c4..fd3904406ce 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -667,6 +667,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/static/src/less/website.ui.components.less b/addons/website/static/src/less/website.ui.components.less index 09f536e6453..9fdc7b6a477 100644 --- a/addons/website/static/src/less/website.ui.components.less +++ b/addons/website/static/src/less/website.ui.components.less @@ -244,7 +244,7 @@ body .modal { } // LOGIN FORM -.oe_login_form { +.oe_login_form, .oe_signup_form { max-width: 300px; position: relative; margin: 50px auto; diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index 73008844b7c..343fcf1e648 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -911,7 +911,14 @@ Sitemap: sitemap.xml "fields": ["name", "address", "phone", "mobile", "fax", "email"]}'/> - Visualize on Google Maps + + + + + + + Visualize on Google Maps +