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 8ddfdfe1107..106579c7195 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -526,7 +526,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/auth_oauth/controllers/main.py b/addons/auth_oauth/controllers/main.py index b7c1f13cf6b..cd3d04b043d 100644 --- a/addons/auth_oauth/controllers/main.py +++ b/addons/auth_oauth/controllers/main.py @@ -119,8 +119,6 @@ class OAuthLogin(Home): @http.route() def web_auth_reset_password(self, *args, **kw): providers = self.list_providers() - if len(providers) == 1: - werkzeug.exceptions.abort(werkzeug.utils.redirect(providers[0]['auth_link'], 303)) response = super(OAuthLogin, self).web_auth_reset_password(*args, **kw) response.qcontext.update(providers=providers) return response 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/models/project.py b/addons/project/models/project.py index b645f7fa867..b14f0a60db4 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -294,7 +294,10 @@ class Task(models.Model): def _get_default_stage_id(self): """ Gives default stage_id """ - return self.stage_find(self.env.context.get('default_project_id'), [('fold', '=', False)]) + project_id = self.env.context.get('default_project_id') + if not project_id: + return False + return self.stage_find(project_id, [('fold', '=', False)]) @api.multi def _read_group_stage_ids(self, domain, read_group_order=None, access_rights_uid=None): @@ -390,6 +393,10 @@ class Task(models.Model): def _onchange_project(self): if self.project_id: self.partner_id = self.project_id.partner_id + self.stage_id = self.stage_find(self.project_id.id, [('fold', '=', False)]) + else: + self.partner_id = False + self.stage_id = False @api.onchange('user_id') def _onchange_user(self): diff --git a/addons/project_issue/models/project_issue.py b/addons/project_issue/models/project_issue.py index 66bed3ea24e..e93dc54688b 100644 --- a/addons/project_issue/models/project_issue.py +++ b/addons/project_issue/models/project_issue.py @@ -15,7 +15,10 @@ class ProjectIssue(models.Model): @api.model def _get_default_stage_id(self): - return self.stage_find(self.env.context.get('default_project_id'), [('fold', '=', False)]) + project_id = self.env.context.get('default_project_id') + if not project_id: + return False + return self.stage_find(project_id, [('fold', '=', False)]) name = fields.Char(string='Issue', required=True) active = fields.Boolean(default=True) @@ -150,6 +153,11 @@ class ProjectIssue(models.Model): if self.project_id: self.partner_id = self.project_id.partner_id.id self.email_from = self.project_id.partner_id.email + self.stage_id = self.stage_find(self.project_id.id, [('fold', '=', False)]) + else: + self.partner_id = False + self.email_from = False + self.stage_id = False @api.onchange('task_id') def _onchange_task_id(self): diff --git a/addons/purchase/invoice.py b/addons/purchase/invoice.py index 1871117a07b..c3e45321371 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 a8cee106183..f8e2c07cde1 100644 --- a/addons/purchase/purchase.py +++ b/addons/purchase/purchase.py @@ -74,12 +74,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_pack_operation.py b/addons/stock/models/stock_pack_operation.py index a8cf2aa3357..c306eac1dae 100644 --- a/addons/stock/models/stock_pack_operation.py +++ b/addons/stock/models/stock_pack_operation.py @@ -77,9 +77,9 @@ class PackOperation(models.Model): def _set_is_done(self): # TDE FIXME: whuuuut ??? if not self.product_id: - if self.processed_boolean and self.qty_done == 0: + if self.is_done and self.qty_done == 0: self.qty_done = 1.0 - if not self.processed_boolean and self.qty_done != 0: + if not self.is_done and self.qty_done != 0: self.qty_done = 0.0 def _get_remaining_prod_quantities(self): diff --git a/addons/stock/models/stock_quant.py b/addons/stock/models/stock_quant.py index f9ece848bd2..5c86fd93d77 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.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/report/report_stockpicking_operations.xml b/addons/stock/report/report_stockpicking_operations.xml index bd77df3eaf4..891d3b92942 100644 --- a/addons/stock/report/report_stockpicking_operations.xml +++ b/addons/stock/report/report_stockpicking_operations.xml @@ -80,13 +80,14 @@

+ -
Product Quantity Source + Barcode @@ -108,7 +109,7 @@ - + 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/subscription/models/subscription.py b/addons/subscription/models/subscription.py index 83381822645..8c55ce016c3 100644 --- a/addons/subscription/models/subscription.py +++ b/addons/subscription/models/subscription.py @@ -74,13 +74,17 @@ class Subscription(models.Model): 'nextcall': subscription.date_init, 'model': self._name, 'args': repr([[subscription.id]]), - 'function': 'model_copy', + 'function': '_cron_model_copy', 'priority': 6, 'user_id': subscription.user_id.id } cron = self.env['ir.cron'].create(cron_data) subscription.write({'cron_id': cron.id, 'state': 'running'}) + @api.model + def _cron_model_copy(self, ids): + self.browse(ids).model_copy() + @api.multi def model_copy(self): for subscription in self.filtered(lambda sub: sub.cron_id): diff --git a/addons/theme_bootswatch/__openerp__.py b/addons/theme_bootswatch/__openerp__.py index 610a69ffbd1..e3446ea4a1f 100644 --- a/addons/theme_bootswatch/__openerp__.py +++ b/addons/theme_bootswatch/__openerp__.py @@ -2,7 +2,7 @@ 'name': 'Bootswatch Theme', 'summary': 'Support for Bootswatch themes in master', 'description': 'This theme module is exclusively for master to keep the support of Bootswatch themes which were previously part of the website module in 8.0.', - 'category': 'Website', + 'category': 'Theme', 'sequence': 900, 'version': '1.0', 'depends': ['website'], diff --git a/addons/theme_default/__openerp__.py b/addons/theme_default/__openerp__.py index 2980862caa5..d7a5d4bb1a6 100644 --- a/addons/theme_default/__openerp__.py +++ b/addons/theme_default/__openerp__.py @@ -1,7 +1,7 @@ { 'name': 'Default Theme', 'description': 'Default website theme to showcase customization possibilities.', - 'category': 'Hidden', + 'category': 'Theme/Hidden', 'sequence': 1000, 'version': '1.0', 'depends': ['website'], diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index f991d5eca07..508f623a75b 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -631,8 +631,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 eea48f9a6b6..362c9a0bab5 100644 --- a/addons/web/static/src/js/views/form_widgets.js +++ b/addons/web/static/src/js/views/form_widgets.js @@ -1435,7 +1435,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 d89929bb13d..0c03c2cbb8e 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 @@ -244,7 +244,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/website/models/website.py b/addons/website/models/website.py index f45d9b505c3..39ab098b96c 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 ab91b6fa199..535be34d01c 100644 --- a/addons/website/static/src/less/website.ui.components.less +++ b/addons/website/static/src/less/website.ui.components.less @@ -250,7 +250,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 b28c06aa68f..fbe2acf3c0c 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -914,7 +914,14 @@ Sitemap: sitemap.xml "fields": ["name", "address", "phone", "mobile", "fax", "email"]}'/> - Visualize on Google Maps + + + + + + + Visualize on Google Maps +