diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py index d2e5a5bfc5d..f9013d065f0 100644 --- a/addons/account/models/account_payment.py +++ b/addons/account/models/account_payment.py @@ -114,18 +114,26 @@ class account_register_payments(models.TransientModel): @api.model def _compute_payment_amount(self, invoice_ids): - payment_currency = self.currency_id or self.journal_id.currency_id or self.journal_id.company_id.currency_id + payment_currency = self.currency_id or self.journal_id.currency_id or self.journal_id.company_id.currency_id or invoice_ids and invoice_ids[0].currency_id total = 0 for inv in invoice_ids: if inv.currency_id == payment_currency: - total += MAP_INVOICE_TYPE_PAYMENT_SIGN[inv.type] * inv.residual_company_signed + total += MAP_INVOICE_TYPE_PAYMENT_SIGN[inv.type] * inv.residual_signed else: amount_residual = inv.company_currency_id.with_context(date=self.payment_date).compute( inv.residual_company_signed, payment_currency) total += MAP_INVOICE_TYPE_PAYMENT_SIGN[inv.type] * amount_residual return total + @api.onchange('journal_id') + def _onchange_journal(self): + res = super(account_register_payments, self)._onchange_journal() + active_ids = self._context.get('active_ids') + invoices = self.env['account.invoice'].browse(active_ids) + self.amount = abs(self._compute_payment_amount(invoices)) + return res + @api.model def default_get(self, fields): rec = super(account_register_payments, self).default_get(fields) diff --git a/addons/account_asset/models/account_asset.py b/addons/account_asset/models/account_asset.py index 3b29938007c..410ad7e8d5c 100644 --- a/addons/account_asset/models/account_asset.py +++ b/addons/account_asset/models/account_asset.py @@ -340,7 +340,9 @@ class AccountAssetAsset(models.Model): def set_to_close(self): move_ids = self._get_disposal_moves() if move_ids: - self._return_disposal_view(move_ids) + return self._return_disposal_view(move_ids) + # Fallback, as if we just clicked on the smartbutton + return self.open_entries() @api.multi def set_to_draft(self): diff --git a/addons/barcodes/README.rst b/addons/barcodes/README.rst index 696ab62fbc5..62739b10fe4 100644 --- a/addons/barcodes/README.rst +++ b/addons/barcodes/README.rst @@ -106,7 +106,7 @@ The purpose of this component is to listen to keypresses to detect barcodes, the on core.bus inside a 'barcode_event'. All keypress events are buffered until there is no more keypress during 50ms or a carriage return / tab is inputted (because most barcode scanners use this as a suffix). -If the buffered keys looks like a barcode (match the the regexp /.{3,}[\n\r\t]*), an event is triggered : +If the buffered keys looks like a barcode, an event is triggered : core.bus.trigger('barcode_scanned', barcode); Otherwise, the keypresses are 'resent'. However, for security reasons, a keypress event programmatically crafted doesn't trigger native browser behaviors. For this reason, BarcodeEvents doesn't intercept keypresses diff --git a/addons/board/static/src/js/dashboard.js b/addons/board/static/src/js/dashboard.js index 5548a5d2adf..1b00b38268f 100644 --- a/addons/board/static/src/js/dashboard.js +++ b/addons/board/static/src/js/dashboard.js @@ -255,7 +255,7 @@ FormRenderer.include({ } var view = _.find(action.views, function (descr) { return descr[1] === params.viewType; - }); + }) || [false, params.viewType]; return self.loadViews(action.res_model, context, [view]) .then(function (viewsInfo) { var viewInfo = viewsInfo[params.viewType]; diff --git a/addons/calendar/models/mail_activity.py b/addons/calendar/models/mail_activity.py index 3efcea70a3b..0741658a087 100644 --- a/addons/calendar/models/mail_activity.py +++ b/addons/calendar/models/mail_activity.py @@ -24,7 +24,7 @@ class MailActivity(models.Model): 'default_res_id': self.env.context.get('default_res_id'), 'default_res_model': self.env.context.get('default_res_model'), 'default_name': self.summary, - 'default_description': self.note and tools.html2plaintext(self.note) or '', + 'default_description': self.note and tools.html2plaintext(self.note).strip() or '', 'default_activity_ids': [(6, 0, self.ids)], } return action diff --git a/addons/crm/models/crm_team.py b/addons/crm/models/crm_team.py index e7ed3a61e08..f24f253d230 100644 --- a/addons/crm/models/crm_team.py +++ b/addons/crm/models/crm_team.py @@ -149,14 +149,22 @@ class Team(models.Model): tree_view_id = self.env.ref('crm.crm_case_tree_view_oppor').id form_view_id = self.env.ref('crm.crm_case_form_view_oppor').id kanb_view_id = self.env.ref('crm.crm_case_kanban_view_leads').id - action['views'] = [ - [kanb_view_id, 'kanban'], - [tree_view_id, 'tree'], - [form_view_id, 'form'], - [False, 'graph'], - [False, 'calendar'], - [False, 'pivot'] - ] + + mode_string = action.get('view_mode', '') + view_mode = mode_string.split(',') if mode_string else ['kanban', 'tree', 'form', 'graph', 'calendar', 'pivot'] + dict_views = { + 'kanban': kanb_view_id, + 'tree': tree_view_id, + 'form': form_view_id, + 'graph': False, + 'calendar': False, + 'pivot': False, + } + + # Sort the views according to the view mode of the action + # which can be modified via studio, or technical menu for that matter + action['views'] = [[dict_views.get(view_type, False), view_type] for view_type in view_mode] + action['context'] = action_context return action diff --git a/addons/purchase/models/stock.py b/addons/purchase/models/stock.py index 86dd0635bdc..039699bd235 100644 --- a/addons/purchase/models/stock.py +++ b/addons/purchase/models/stock.py @@ -60,6 +60,10 @@ class StockMove(models.Model): vals['purchase_line_id'] = self.purchase_line_id.id return vals + def _clean_merged(self): + super(StockMove, self)._clean_merged() + self.write({'created_purchase_line_id': False}) + def _action_done(self): res = super(StockMove, self)._action_done() self.mapped('purchase_line_id').sudo()._update_received_qty() diff --git a/addons/sale_stock/tests/test_sale_stock.py b/addons/sale_stock/tests/test_sale_stock.py index 42c3ca84a98..6f67e4817aa 100644 --- a/addons/sale_stock/tests/test_sale_stock.py +++ b/addons/sale_stock/tests/test_sale_stock.py @@ -178,14 +178,14 @@ class TestSaleStock(TestSale): # Check invoice self.assertEqual(self.so.invoice_status, 'to invoice', 'Sale Stock: so invoice_status should be "to invoice" instead of "%s" after picking return' % self.so.invoice_status) - self.assertAlmostEqual(self.so.order_line[0].qty_delivered, 3.0, 'Sale Stock: delivered quantity should be 3.0 instead of "%s" after picking return' % self.so.order_line[0].qty_delivered) + self.assertAlmostEqual(self.so.order_line[0].qty_delivered, 3.0, msg='Sale Stock: delivered quantity should be 3.0 instead of "%s" after picking return' % self.so.order_line[0].qty_delivered) # let's do an invoice with refunds adv_wiz = self.env['sale.advance.payment.inv'].with_context(active_ids=[self.so.id]).create({ 'advance_payment_method': 'all', }) adv_wiz.with_context(open_invoices=True).create_invoices() self.inv_2 = self.so.invoice_ids.filtered(lambda r: r.state == 'draft') - self.assertAlmostEqual(self.inv_2.invoice_line_ids[0].quantity, 2.0, 'Sale Stock: refund quantity on the invoice should be 2.0 instead of "%s".' % self.inv_2.invoice_line_ids[0].quantity) + self.assertAlmostEqual(self.inv_2.invoice_line_ids[0].quantity, 2.0, msg='Sale Stock: refund quantity on the invoice should be 2.0 instead of "%s".' % self.inv_2.invoice_line_ids[0].quantity) self.assertEqual(self.so.invoice_status, 'no', 'Sale Stock: so invoice_status should be "no" instead of "%s" after invoicing the return' % self.so.invoice_status) def test_03_sale_stock_delivery_partial(self): diff --git a/addons/stock/models/procurement.py b/addons/stock/models/procurement.py index fd08b362949..64929fa0084 100644 --- a/addons/stock/models/procurement.py +++ b/addons/stock/models/procurement.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from collections import defaultdict +from collections import OrderedDict from datetime import datetime from dateutil.relativedelta import relativedelta from odoo.tools.misc import split_every @@ -318,9 +318,18 @@ class ProcurementGroup(models.Model): orderpoints_noprefetch = orderpoints_noprefetch[1000:] # Calculate groups that can be executed together - location_data = defaultdict(lambda: dict(products=self.env['product.product'], orderpoints=self.env['stock.warehouse.orderpoint'], groups=list())) + location_data = OrderedDict() + + def makedefault(): + return { + 'products': self.env['product.product'], + 'orderpoints': self.env['stock.warehouse.orderpoint'], + 'groups': [] + } + for orderpoint in orderpoints: key = self._procurement_from_orderpoint_get_grouping_key([orderpoint.id]) + location_data[key] = makedefault() location_data[key]['products'] += orderpoint.product_id location_data[key]['orderpoints'] += orderpoint location_data[key]['groups'] = self._procurement_from_orderpoint_get_groups([orderpoint.id]) diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 1feda768342..892684a8b00 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -561,6 +561,10 @@ class StockMove(models.Model): move.product_uom.id, move.restrict_partner_id.id, move.scrapped, move.origin_returned_move_id.id ] + def _clean_merged(self): + """Cleanup hook used when merging moves""" + self.write({'propagate': False}) + def _merge_moves(self, merge_into=False): """ This method will, for each move in `self`, go up in their linked picking and try to find in their existing moves a candidate into which we can merge the move. @@ -598,7 +602,7 @@ class StockMove(models.Model): if moves_to_unlink: # We are using propagate to False in order to not cancel destination moves merged in moves[0] - moves_to_unlink.write({'propagate': False}) + moves_to_unlink._clean_merged() moves_to_unlink._action_cancel() moves_to_unlink.sudo().unlink() return (self | self.env['stock.move'].concat(*moves_to_merge)) - moves_to_unlink diff --git a/addons/web/static/src/js/views/kanban/kanban_column_progressbar.js b/addons/web/static/src/js/views/kanban/kanban_column_progressbar.js index b597ccf1c60..7e1f16c9fb0 100644 --- a/addons/web/static/src/js/views/kanban/kanban_column_progressbar.js +++ b/addons/web/static/src/js/views/kanban/kanban_column_progressbar.js @@ -142,6 +142,7 @@ var KanbanColumnProgressBar = Widget.extend({ // Display and animate the progress bars var barNumber = 0; + var barMinWidth = 6; // In % _.each(self.colors, function (val, key) { var $bar = self.$bars[val]; var count = self.subgroupCounts && self.subgroupCounts[key] || 0; @@ -168,16 +169,15 @@ var KanbanColumnProgressBar = Widget.extend({ $bar.addClass('o_bar_has_records'); // Make sure every bar that has records has some space // and that everything adds up to 100% - var minWidth = 6*barNumber; - var maxWidth = 100 - minWidth; + var maxWidth = 100 - barMinWidth * barNumber; self.$('.progress-bar.o_bar_has_records').css('max-width', maxWidth + '%'); - self.$('.progress-bar.o_bar_has_records').css('min-width', minWidth + '%'); $bar.css('width', (count * 100 / self.groupCount) + '%'); barNumber++; } else { $bar.css('width', ''); } }); + this.$('.progress-bar.o_bar_has_records').css('min-width', barMinWidth + '%'); // Display and animate the counter number var start = this.prevTotalCounterValue; diff --git a/addons/web/static/src/js/views/pivot/pivot_model.js b/addons/web/static/src/js/views/pivot/pivot_model.js index 76e22c8b4e5..6de39c0b535 100644 --- a/addons/web/static/src/js/views/pivot/pivot_model.js +++ b/addons/web/static/src/js/views/pivot/pivot_model.js @@ -524,7 +524,7 @@ var PivotModel = AbstractModel.extend({ _loadData: function () { var self = this; var groupBys = []; - var rowGroupBys = this.data.groupedBy.length ? this.data.groupedBy : this.initialRowGroupBys; + var rowGroupBys = !_.isEmpty(this.data.groupedBy) ? this.data.groupedBy : this.initialRowGroupBys; var colGroupBys = this.data.colGroupBys; var fields = [].concat(rowGroupBys, colGroupBys, this.data.measures); @@ -600,7 +600,7 @@ var PivotModel = AbstractModel.extend({ }); var index = 0; - var rowGroupBys = this.data.groupedBy.length ? this.data.groupedBy : this.initialRowGroupBys; + var rowGroupBys = !_.isEmpty(this.data.groupedBy) ? this.data.groupedBy : this.initialRowGroupBys; var colGroupBys = this.data.colGroupBys; var datapt, row, col, attrs, cell_value; var main_row_header, main_col_header; diff --git a/addons/website_sale_digital/controllers/main.py b/addons/website_sale_digital/controllers/main.py index c53f27d7336..39d0492260a 100644 --- a/addons/website_sale_digital/controllers/main.py +++ b/addons/website_sale_digital/controllers/main.py @@ -44,7 +44,7 @@ class WebsiteSaleDigital(CustomerPortal): product_id = product.id template = product.product_tmpl_id att = Attachment.search_read( - domain=['|', '&', ('res_model', '=', product._name), ('res_id', '=', product_id), '&', ('res_model', '=', template._name), '&', ('res_id', '=', template.id), ('product_downloadable', '=', True)], + domain=['|', '&', ('res_model', '=', product._name), ('res_id', '=', product_id), '&', ('res_model', '=', template._name), ('res_id', '=', template.id), ('product_downloadable', '=', True)], fields=['name', 'write_date'], order='write_date desc', ) diff --git a/doc/cla/corporate/international-business-hub.md b/doc/cla/corporate/international-business-hub.md new file mode 100644 index 00000000000..b2989af2cc2 --- /dev/null +++ b/doc/cla/corporate/international-business-hub.md @@ -0,0 +1,17 @@ +Switzerland, 2018-07-31 + +International Business Hub 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, + +Sandro Pignataro pignatarosandro@gmail.com https://github.com/Pigna74 + +List of contributors: + +Guido Notari guido.notari@gmail.com https://github.com/gnotari +Matteo Bilotta byloth@gmail.com https://github.com/Byloth + diff --git a/odoo/service/db.py b/odoo/service/db.py index 8f3c24d7d8a..95723f36690 100644 --- a/odoo/service/db.py +++ b/odoo/service/db.py @@ -71,7 +71,10 @@ def _initialize_db(id, db_name, demo, lang, user_password, login='admin', countr if country_code: countries = env['res.country'].search([('code', 'ilike', country_code)]) if countries: - env['res.company'].browse(1).country_id = countries[0] + comp_local = {'country_id': countries[0].id} + if countries[0].currency_id: + comp_local['currency_id'] = countries[0].currency_id.id + env['res.company'].browse(1).write(comp_local) # update admin's password and lang and login values = {'password': user_password, 'lang': lang}