diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 4da13596b88..b7c1985719f 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -503,16 +503,19 @@ class AccountInvoice(models.Model): """ res = super(AccountInvoice, self).default_get(default_fields) - if not res.get('type', False) == 'out_invoice' or not 'company_id' in res: + if res.get('type', False) not in ('out_invoice', 'in_refund') or not 'company_id' in res: return res - company = self.env['res.company'].browse(res['company_id']) - if company.partner_id: - partner_bank_result = self.env['res.partner.bank'].search([('partner_id', '=', company.partner_id.id)], limit=1) - if partner_bank_result: - res['partner_bank_id'] = partner_bank_result.id + partner_bank_result = self._get_partner_bank_id(res['company_id']) + if partner_bank_result: + res['partner_bank_id'] = partner_bank_result.id return res + def _get_partner_bank_id(self, company_id): + company = self.env['res.company'].browse(company_id) + if company.partner_id: + return self.env['res.partner.bank'].search([('partner_id', '=', company.partner_id.id)], limit=1) + @api.model def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False): def get_view_id(xid, name): @@ -1369,6 +1372,11 @@ class AccountInvoice(models.Model): values['payment_term_id'] = False values['refund_invoice_id'] = invoice.id + if values['type'] == 'in_refund': + partner_bank_result = self._get_partner_bank_id(values['company_id']) + if partner_bank_result: + values['partner_bank_id'] = partner_bank_result.id + if date: values['date'] = date if description: diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index d61b2aa60f2..2c29947ec6c 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -143,8 +143,9 @@ class AccountMove(models.Model): def post(self): invoice = self._context.get('invoice', False) self._post_validate() + # Create the analytic lines in batch is faster as it leads to less cache invalidation. + self.mapped('line_ids').create_analytic_lines() for move in self: - move.line_ids.create_analytic_lines() if move.name == '/': new_name = False journal = move.journal_id diff --git a/addons/account/tests/test_account_supplier_invoice.py b/addons/account/tests/test_account_supplier_invoice.py index d1a6ef46902..04baf780631 100644 --- a/addons/account/tests/test_account_supplier_invoice.py +++ b/addons/account/tests/test_account_supplier_invoice.py @@ -108,3 +108,39 @@ class TestAccountSupplierInvoice(AccountingTestCase): #I cancel the account move which is in posted state and verifies that it gives warning message with self.assertRaises(Warning): invoice.move_id.button_cancel() + + def test_vendor_bill_refund(self): + invoice_account = self.env['account.account'].search( + [('user_type_id', '=', self.env.ref('account.data_account_type_receivable').id)], limit=1) + invoice_line_account = self.env['account.account'].search( + [('user_type_id', '=', self.env.ref('account.data_account_type_expenses').id)], limit=1) + + if self.env.ref('base.main_partner').bank_account_count > 0: + bank = self.env['res.partner.bank'].search([('partner_id', '=', self.env.ref('base.main_partner').id)], limit=1) + + else: + bank = self.env['res.partner.bank'].create({ + 'acc_number': '12345678910', + 'partner_id': self.env.ref('base.main_partner').id, + }) + + invoice_id = self.env['account.invoice'].create({ + 'name': 'invoice test refund', + 'reference_type': 'none', + 'partner_id': self.env.ref("base.res_partner_2").id, + 'account_id': invoice_account.id, + 'currency_id': self.env.ref('base.USD').id, + 'type': 'in_invoice', + }) + self.env['account.invoice.line'].create({ + 'product_id': self.env.ref("product.product_product_4").id, + 'quantity': 1, + 'price_unit': 15.0, + 'invoice_id': invoice_id.id, + 'name': 'something', + 'account_id': invoice_line_account.id, + }) + + refund_invoices = invoice_id.refund() + + self.assertEqual(refund_invoices.partner_bank_id, bank) diff --git a/addons/account_asset/models/account.py b/addons/account_asset/models/account.py index 7a2a3bc4968..1ad55554061 100644 --- a/addons/account_asset/models/account.py +++ b/addons/account_asset/models/account.py @@ -18,7 +18,5 @@ class AccountMove(models.Model): @api.multi def post(self): - for move in self: - for depreciation_line in move.asset_depreciation_ids: - depreciation_line.post_lines_and_close_asset() + self.mapped('asset_depreciation_ids').post_lines_and_close_asset() return super(AccountMove, self).post() diff --git a/addons/account_asset/models/account_asset.py b/addons/account_asset/models/account_asset.py index 85c2c659119..7abf9acd7cd 100644 --- a/addons/account_asset/models/account_asset.py +++ b/addons/account_asset/models/account_asset.py @@ -500,8 +500,12 @@ class AccountAssetDepreciationLine(models.Model): @api.multi def create_move(self, post_move=True): created_moves = self.env['account.move'] + # `line.move_id` was invalidated from the cache at each iteration + # To prevent to refetch `move_id` of all lines at each iteration just to check a UserError, + # we use an intermediar dict which stores the information the UserError check requires. + line_moves = {line: line.move_id for line in self} for line in self: - if line.move_id: + if line_moves[line]: raise UserError(_('This depreciation is already linked to a journal entry! Please post or delete it.')) move_vals = self._prepare_move(line) move = self.env['account.move'].create(move_vals) @@ -547,6 +551,7 @@ class AccountAssetDepreciationLine(models.Model): 'line_ids': [(0, 0, move_line_1), (0, 0, move_line_2)], } return move_vals + line_moves[line] = move def _prepare_move_grouped(self): category_id = self[0].asset_id.category_id # we can suppose that all lines have the same category @@ -602,12 +607,19 @@ class AccountAssetDepreciationLine(models.Model): @api.multi def post_lines_and_close_asset(self): # we re-evaluate the assets to determine whether we can close them + # `message_post` invalidates the (whole) cache + # preprocess the assets and lines in which a message should be posted, + # and then post in batch will prevent the re-fetch of the same data over and over. + assets_to_close = self.env['account.asset.asset'] for line in self: - line.log_message_when_posted() asset = line.asset_id if asset.currency_id.is_zero(asset.value_residual): - asset.message_post(body=_("Document closed.")) - asset.write({'state': 'close'}) + assets_to_close |= asset + self.log_message_when_posted() + assets_to_close.write({'state': 'close'}) + for asset in assets_to_close: + asset.message_post(body=_("Document closed.")) + @api.multi def log_message_when_posted(self): @@ -620,6 +632,10 @@ class AccountAssetDepreciationLine(models.Model): message += '%s' % values return message + # `message_post` invalidates the (whole) cache + # preprocess the assets in which messages should be posted, + # and then post in batch will prevent the re-fetch of the same data over and over. + assets_to_post = {} for line in self: if line.move_id and line.move_id.state == 'draft': partner_name = line.asset_id.partner_id.name @@ -628,7 +644,10 @@ class AccountAssetDepreciationLine(models.Model): if partner_name: msg_values[_('Partner')] = partner_name msg = _format_message(_('Depreciation line posted.'), msg_values) - line.asset_id.message_post(body=msg) + assets_to_post.setdefault(line.asset_id, []).append(msg) + for asset, messages in assets_to_post.items(): + for msg in messages: + asset.message_post(body=msg) @api.multi def unlink(self): diff --git a/addons/barcodes/static/src/js/barcode_events.js b/addons/barcodes/static/src/js/barcode_events.js index 0f2849525de..70463cd590e 100644 --- a/addons/barcodes/static/src/js/barcode_events.js +++ b/addons/barcodes/static/src/js/barcode_events.js @@ -56,20 +56,18 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { this.isChromeMobile = isMobile && navigator.userAgent.match(/Chrome/i); // Creates an input who will receive the barcode scanner value. - if (this.isChromeMobile) { - this.$barcodeInput = $('', { - name: 'barcode', - type: 'text', - css: { - 'position': 'fixed', - 'top': '50%', - 'transform': 'translateY(-50%)', - 'z-index': '-1', - }, - }); - // Avoid to show autocomplete for a non appearing input - this.$barcodeInput.attr('autocomplete', 'off'); - } + this.$barcodeInput = $('', { + name: 'barcode', + type: 'text', + css: { + 'position': 'fixed', + 'top': '50%', + 'transform': 'translateY(-50%)', + 'z-index': '-1', + }, + }); + // Avoid to show autocomplete for a non appearing input + this.$barcodeInput.attr('autocomplete', 'off'); this.__blurBarcodeInput = _.debounce(this._blurBarcodeInput, this.inputTimeOut); }, @@ -220,7 +218,8 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { * @param {jQuery.Event} e keydown event */ _listenBarcodeScanner: function (e) { - if (!$('input:text:focus, textarea:focus, [contenteditable]:focus').length) { + if ($(document.activeElement).not('input:text, textarea, [contenteditable], ' + + '[type="email"], [type="number"], [type="password"], [type="tel"]').length) { $('body').append(this.$barcodeInput); this.$barcodeInput.focus(); } @@ -263,11 +262,9 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { * @private */ _blurBarcodeInput: function () { - if (this.$barcodeInput) { - // Close the virtual keyboard on mobile browsers - // FIXME: actually we can't prevent keyboard from opening - this.$barcodeInput.val('').blur(); - } + // Close the virtual keyboard on mobile browsers + // FIXME: actually we can't prevent keyboard from opening + this.$barcodeInput.val('').blur(); }, start: function(prevent_key_repeat){ diff --git a/addons/barcodes/static/tests/barcode_mobile_tests.js b/addons/barcodes/static/tests/barcode_mobile_tests.js new file mode 100644 index 00000000000..cb26da64c74 --- /dev/null +++ b/addons/barcodes/static/tests/barcode_mobile_tests.js @@ -0,0 +1,74 @@ +odoo.define('barcodes.barcode_mobile_tests', function () { + "use strict"; + + QUnit.module('Barcodes', {}, function () { + + QUnit.module('Barcodes Mobile'); + + QUnit.test('barcode field automatically focus behavior', function (assert) { + assert.expect(10); + + // Mock Chrome mobile environment + var barcodeEvents = odoo.__DEBUG__.services["barcodes.BarcodeEvents"].BarcodeEvents; + var __isChromeMobile = barcodeEvents.isChromeMobile; + barcodeEvents.isChromeMobile = true; + // Rebind keyboard events + barcodeEvents.stop(); + barcodeEvents.start(); + + var $form = $( + '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + '' + + '
'); + $('#qunit-fixture').append($form); + + // Some elements doesn't need to keep the focus + $('body').keydown(); + assert.strictEqual(document.activeElement.name, 'barcode', + "hidden barcode input should have the focus"); + + var $element = $form.find('select'); + $element.focus().keydown(); + assert.strictEqual(document.activeElement.name, 'barcode', + "hidden barcode input should have the focus"); + + // Those elements absolutely need to keep the focus: + // inputs elements: + var keepFocusedElements = ['email', 'number', 'password', 'tel', + 'text', 'explicit_text']; + for (var i = 0; i < keepFocusedElements.length; ++i) { + $element = $form.find('input[name=' + keepFocusedElements[i] + ']'); + $element.focus().keydown(); + assert.strictEqual(document.activeElement, $element[0], + "input " + keepFocusedElements[i] + " should keep focus"); + } + // textarea element + $element = $form.find('textarea'); + $element.focus().keydown(); + assert.strictEqual(document.activeElement, $element[0], + "textarea should keep focus"); + // contenteditable elements + $element = $form.find('[contenteditable=true]'); + $element.focus().keydown(); + assert.strictEqual(document.activeElement, $element[0], + "contenteditable should keep focus"); + + $('#qunit-fixture').empty(); + barcodeEvents.isChromeMobile = __isChromeMobile; + // Rebind keyboard events + barcodeEvents.stop(); + barcodeEvents.start(); + }); + }); + }); diff --git a/addons/barcodes/views/barcodes_templates.xml b/addons/barcodes/views/barcodes_templates.xml index cb16a40c178..45b77bd3301 100644 --- a/addons/barcodes/views/barcodes_templates.xml +++ b/addons/barcodes/views/barcodes_templates.xml @@ -16,4 +16,10 @@ + + diff --git a/addons/hw_screen/controllers/main.py b/addons/hw_screen/controllers/main.py index b8e59fbfdd6..b4388e8de3c 100644 --- a/addons/hw_screen/controllers/main.py +++ b/addons/hw_screen/controllers/main.py @@ -150,7 +150,7 @@ class HardwareScreen(web.Home): -
+

Odoo Point of Sale

diff --git a/addons/hw_screen/static/src/js/worker.js b/addons/hw_screen/static/src/js/worker.js index 55e389e78ab..b67d611c4ca 100644 --- a/addons/hw_screen/static/src/js/worker.js +++ b/addons/hw_screen/static/src/js/worker.js @@ -32,8 +32,8 @@ $("head").append($parsedHTML.find(".resources").html()); } - $(".container").html($parsedHTML.find('.pos-customer_facing_display').html()); - $(".container").attr('class', 'container').addClass($parsedHTML.find('.pos-customer_facing_display').attr('class')); + $(".container-fluid").html($parsedHTML.find('.pos-customer_facing_display').html()); + $(".container-fluid").attr('class', 'container-fluid').addClass($parsedHTML.find('.pos-customer_facing_display').attr('class')); var d = $('.pos_orderlines_list'); d.scrollTop(d.prop("scrollHeight")); diff --git a/addons/l10n_be_intrastat/wizard/xml_decl.py b/addons/l10n_be_intrastat/wizard/xml_decl.py index dfea17c3e70..d27f677d431 100644 --- a/addons/l10n_be_intrastat/wizard/xml_decl.py +++ b/addons/l10n_be_intrastat/wizard/xml_decl.py @@ -223,11 +223,13 @@ class XmlDeclaration(models.TransientModel): if dispatchmode: mode1 = 'out_invoice' mode2 = 'in_refund' + extrf = "29" declcode = self._get_expedition_code(extendedmode) declform = self._get_expedition_form(extendedmode) else: mode1 = 'in_invoice' mode2 = 'out_refund' + extrf = "19" declcode = self._get_reception_code(extendedmode) declform = self._get_reception_form(extendedmode) @@ -272,7 +274,7 @@ class XmlDeclaration(models.TransientModel): invoicelines = self.env['account.invoice.line'].browse(invoicelines_ids) for inv_line in invoicelines: - linekey = self._get_intrastat_linekey(declcode, inv_line, dispatchmode, extendedmode) + linekey = self._get_intrastat_linekey(extrf, inv_line, dispatchmode, extendedmode) if linekey is None: continue diff --git a/addons/l10n_fr_fec/wizard/account_fr_fec.py b/addons/l10n_fr_fec/wizard/account_fr_fec.py index 88284475764..b9e8af84869 100644 --- a/addons/l10n_fr_fec/wizard/account_fr_fec.py +++ b/addons/l10n_fr_fec/wizard/account_fr_fec.py @@ -9,7 +9,7 @@ from datetime import datetime from odoo import api, fields, models, _ from odoo.exceptions import Warning -from odoo.tools import pycompat, DEFAULT_SERVER_DATE_FORMAT +from odoo.tools import float_is_zero, pycompat, DEFAULT_SERVER_DATE_FORMAT class AccountFrFec(models.TransientModel): @@ -183,14 +183,16 @@ class AccountFrFec(models.TransientModel): sql_query += ''' GROUP BY aml.account_id, aat.type - HAVING sum(aml.balance) != 0 + HAVING round(sum(aml.balance), %s) != 0 AND aat.type not in ('receivable', 'payable') ''' formatted_date_from = self.date_from.replace('-', '') date_from = datetime.strptime(self.date_from, DEFAULT_SERVER_DATE_FORMAT) formatted_date_year = date_from.year + currency_digits = 2 + self._cr.execute( - sql_query, (formatted_date_year, formatted_date_from, formatted_date_from, formatted_date_from, self.date_from, company.id)) + sql_query, (formatted_date_year, formatted_date_from, formatted_date_from, formatted_date_from, self.date_from, company.id, currency_digits)) for row in self._cr.fetchall(): listrow = list(row) @@ -203,6 +205,8 @@ class AccountFrFec(models.TransientModel): current_amount = float(listrow[11].replace(',', '.')) - float(listrow[12].replace(',', '.')) unaffected_earnings_amount = float(unaffected_earnings_results[11].replace(',', '.')) - float(unaffected_earnings_results[12].replace(',', '.')) listrow_amount = current_amount + unaffected_earnings_amount + if float_is_zero(listrow_amount, precision_digits=currency_digits): + continue if listrow_amount > 0: listrow[11] = str(listrow_amount).replace('.', ',') listrow[12] = '0,00' @@ -270,11 +274,11 @@ class AccountFrFec(models.TransientModel): sql_query += ''' GROUP BY aml.account_id, aat.type, rp.ref, rp.id - HAVING sum(aml.balance) != 0 + HAVING round(sum(aml.balance), %s) != 0 AND aat.type in ('receivable', 'payable') ''' self._cr.execute( - sql_query, (formatted_date_year, formatted_date_from, formatted_date_from, formatted_date_from, self.date_from, company.id)) + sql_query, (formatted_date_year, formatted_date_from, formatted_date_from, formatted_date_from, self.date_from, company.id, currency_digits)) for row in self._cr.fetchall(): listrow = list(row) diff --git a/addons/l10n_mx/data/account_tax_data.xml b/addons/l10n_mx/data/account_tax_data.xml index e182fe60763..01ba23e0d4b 100644 --- a/addons/l10n_mx/data/account_tax_data.xml +++ b/addons/l10n_mx/data/account_tax_data.xml @@ -200,6 +200,7 @@ on_payment + @@ -215,6 +216,7 @@ on_payment + diff --git a/addons/mrp/models/mrp_workorder.py b/addons/mrp/models/mrp_workorder.py index 6935bae7136..3459fbfced3 100644 --- a/addons/mrp/models/mrp_workorder.py +++ b/addons/mrp/models/mrp_workorder.py @@ -368,6 +368,7 @@ class MrpWorkorder(models.Model): move_line.product_uom_qty += self.qty_producing move_line.qty_done += self.qty_producing else: + location_dest_id = production_move.location_dest_id.get_putaway_strategy(self.product_id).id or production_move.location_dest_id.id move_line.create({'move_id': production_move.id, 'product_id': production_move.product_id.id, 'lot_id': self.final_lot_id.id, @@ -376,7 +377,7 @@ class MrpWorkorder(models.Model): 'qty_done': self.qty_producing, 'workorder_id': self.id, 'location_id': production_move.location_id.id, - 'location_dest_id': production_move.location_dest_id.id, + 'location_dest_id': location_dest_id, }) else: production_move.quantity_done += self.qty_producing diff --git a/addons/mrp/tests/test_stock.py b/addons/mrp/tests/test_stock.py index 8b3c557d3e2..908064fd828 100644 --- a/addons/mrp/tests/test_stock.py +++ b/addons/mrp/tests/test_stock.py @@ -6,6 +6,52 @@ from odoo.exceptions import except_orm class TestWarehouse(common.TestMrpCommon): + def setUp(self): + super(TestWarehouse, self).setUp() + + self.stock_location = self.env.ref('stock.stock_location_stock') + self.depot_location = self.env['stock.location'].create({ + 'name': 'Depot', + 'usage': 'internal', + 'location_id': self.stock_location.id, + }) + putaway = self.env['product.putaway'].create({ + 'name': 'putaway stock->depot', + 'fixed_location_ids': [(0, 0, { + 'category_id': self.env.ref('product.product_category_all').id, + 'fixed_location_id': self.depot_location.id, + })] + }) + self.stock_location.write({ + 'putaway_strategy_id': putaway.id, + }) + + self.laptop = self.env.ref("product.product_product_25") + graphics_card = self.env.ref("product.product_product_24") + unit = self.env.ref("product.product_uom_unit") + mrp_routing = self.env.ref("mrp.mrp_routing_0") + + bom_laptop = self.env['mrp.bom'].create({ + 'product_tmpl_id': self.laptop.product_tmpl_id.id, + 'product_qty': 1, + 'product_uom_id': unit.id, + 'bom_line_ids': [(0, 0, { + 'product_id': graphics_card.id, + 'product_qty': 1, + 'product_uom_id': unit.id + })], + 'routing_id': mrp_routing.id + }) + + # Return a new Manufacturing Order for laptop + def new_mo_laptop(): + return self.env['mrp.production'].create({ + 'product_id': self.laptop.id, + 'product_qty': 1, + 'product_uom_id': unit.id, + 'bom_id': bom_laptop.id + }) + self.new_mo_laptop = new_mo_laptop def test_manufacturing_route(self): warehouse_1_stock_manager = self.warehouse_1.sudo(self.user_stock_manager) @@ -100,3 +146,69 @@ class TestWarehouse(common.TestMrpCommon): # scrap_move = production_3.move_raw_ids.filtered(lambda x: x.product_id == self.product_2 and x.scrapped) # self.assertTrue(scrap_move, "There are no any scrap move created for production order.") + + def test_putaway_after_manufacturing_1(self): + """ This test checks a manufactured product without tracking will go to + location defined in putaway strategy. + """ + mo_laptop = self.new_mo_laptop() + + mo_laptop.button_plan() + workorder = mo_laptop.workorder_ids[0] + + workorder.button_start() + workorder.record_production() + mo_laptop.button_mark_done() + + # We check if the laptop go in the depot and not in the stock + move = mo_laptop.move_finished_ids + location_dest = move.move_line_ids.location_dest_id + self.assertEqual(location_dest.id, self.depot_location.id) + self.assertNotEqual(location_dest.id, self.stock_location.id) + + def test_putaway_after_manufacturing_2(self): + """ This test checks a tracked manufactured product will go to location + defined in putaway strategy. + """ + self.laptop.tracking = 'serial' + mo_laptop = self.new_mo_laptop() + + mo_laptop.button_plan() + workorder = mo_laptop.workorder_ids[0] + + workorder.button_start() + serial = self.env['stock.production.lot'].create({'product_id': self.laptop.id}) + workorder.final_lot_id = serial + workorder.record_production() + mo_laptop.button_mark_done() + + # We check if the laptop go in the depot and not in the stock + move = mo_laptop.move_finished_ids + location_dest = move.move_line_ids.location_dest_id + self.assertEqual(location_dest.id, self.depot_location.id) + self.assertNotEqual(location_dest.id, self.stock_location.id) + + def test_putaway_after_manufacturing_3(self): + """ This test checks a tracked manufactured product will go to location + defined in putaway strategy when the production is recorded with + product.produce wizard. + """ + self.laptop.tracking = 'serial' + mo_laptop = self.new_mo_laptop() + serial = self.env['stock.production.lot'].create({'product_id': self.laptop.id}) + + product_produce = self.env['mrp.product.produce'].with_context({ + 'active_id': mo_laptop.id, + 'active_ids': [mo_laptop.id], + }).create({ + 'product_qty': 1.0, + 'lot_id': serial.id, + }) + product_produce.do_produce() + mo_laptop.button_mark_done() + + # We check if the laptop go in the depot and not in the stock + move = mo_laptop.move_finished_ids + location_dest = move.move_line_ids.location_dest_id + self.assertEqual(location_dest.id, self.depot_location.id) + self.assertNotEqual(location_dest.id, self.stock_location.id) diff --git a/addons/mrp/wizard/mrp_product_produce.py b/addons/mrp/wizard/mrp_product_produce.py index 52c08b06b87..289aefcfbde 100644 --- a/addons/mrp/wizard/mrp_product_produce.py +++ b/addons/mrp/wizard/mrp_product_produce.py @@ -133,6 +133,7 @@ class MrpProductProduce(models.TransientModel): existing_move_line.product_uom_qty += self.product_qty existing_move_line.qty_done += self.product_qty else: + location_dest_id = produce_move.location_dest_id.get_putaway_strategy(self.product_id).id or produce_move.location_dest_id.id vals = { 'move_id': produce_move.id, 'product_id': produce_move.product_id.id, @@ -142,7 +143,7 @@ class MrpProductProduce(models.TransientModel): 'qty_done': self.product_qty, 'lot_id': self.lot_id.id, 'location_id': produce_move.location_id.id, - 'location_dest_id': produce_move.location_dest_id.id, + 'location_dest_id': location_dest_id, } self.env['stock.move.line'].create(vals) diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index a2c6b27df62..9aeefd15c06 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -856,6 +856,7 @@ class PosOrder(models.Model): for pack_lot in pack_lots: lot_id, qty = pack_lot['lot_id'], pack_lot['qty'] self.env['stock.move.line'].create({ + 'picking_id': move.picking_id.id, 'move_id': move.id, 'product_id': move.product_id.id, 'product_uom_id': move.product_uom.id, diff --git a/addons/point_of_sale/static/src/css/customer_facing_display.css b/addons/point_of_sale/static/src/css/customer_facing_display.css index 60250405062..47726247def 100644 --- a/addons/point_of_sale/static/src/css/customer_facing_display.css +++ b/addons/point_of_sale/static/src/css/customer_facing_display.css @@ -35,6 +35,7 @@ body .pos-customer_facing_display { font-weight: 300; width: 100%; height: 100%; + padding: 0; -webkit-display: flex; -moz-display: flex; -ms-display: flex; diff --git a/addons/point_of_sale/static/src/less/customer_facing_display.less b/addons/point_of_sale/static/src/less/customer_facing_display.less index 143509ed2a2..699359eb13b 100644 --- a/addons/point_of_sale/static/src/less/customer_facing_display.less +++ b/addons/point_of_sale/static/src/less/customer_facing_display.less @@ -95,6 +95,7 @@ body { font-weight: 300; width: 100%; height: 100%; + padding: 0; .flex-display(); .flex-direction(row); diff --git a/addons/portal/wizard/portal_wizard.py b/addons/portal/wizard/portal_wizard.py index 9fa0e12561e..1a60edf16c1 100644 --- a/addons/portal/wizard/portal_wizard.py +++ b/addons/portal/wizard/portal_wizard.py @@ -87,7 +87,7 @@ class PortalWizardUser(models.TransientModel): partners_error_empty |= wizard_user.partner_id elif email in emails: partners_error_emails |= wizard_user.partner_id - user = self.env['res.users'].sudo().with_context(active_test=False).search([('login', '=', email)]) + user = self.env['res.users'].sudo().with_context(active_test=False).search([('login', '=ilike', email)]) if user: partners_error_user |= wizard_user.partner_id emails.append(email) diff --git a/addons/stock/models/stock_traceability.py b/addons/stock/models/stock_traceability.py index add1b9bd6e5..c094dfdc3c2 100644 --- a/addons/stock/models/stock_traceability.py +++ b/addons/stock/models/stock_traceability.py @@ -79,10 +79,11 @@ class MrpStockReport(models.TransientModel): res_model = '' ref = '' res_id = False - if move_line.picking_id: + picking_id = move_line.picking_id or move_line.move_id.picking_id + if picking_id: res_model = 'stock.picking' - res_id = move_line.picking_id.id - ref = move_line.picking_id.name + res_id = picking_id.id + ref = picking_id.name elif move_line.move_id.inventory_id: res_model = 'stock.inventory' res_id = move_line.move_id.inventory_id.id diff --git a/addons/web/static/lib/qweb/qweb2.js b/addons/web/static/lib/qweb/qweb2.js index 94a0afaa32a..5e504f57222 100644 --- a/addons/web/static/lib/qweb/qweb2.js +++ b/addons/web/static/lib/qweb/qweb2.js @@ -173,7 +173,7 @@ var QWeb2 = { new_dict[as_first] = index === 0; new_dict[as_last] = index + 1 === size; new_dict[as_parity] = (index % 2 == 1 ? 'odd' : 'even'); - if (cur.constructor === Object) { + if (cur && cur.constructor === Object) { this.extend(new_dict, cur); } new_dict[as] = cur; diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js index 6ceafa5d43b..1b93c98b870 100644 --- a/addons/web/static/src/js/views/form/form_renderer.js +++ b/addons/web/static/src/js/views/form/form_renderer.js @@ -346,7 +346,7 @@ var FormRenderer = BasicRenderer.extend({ var visible_buttons = buttons_partition[1]; // Get the unfolded buttons according to window size - var nb_buttons = [2, 2, 4, 6][config.device.size_class] || 7; + var nb_buttons = this._renderButtonBoxNbButtons(); var unfolded_buttons = visible_buttons.slice(0, nb_buttons).concat(invisible_buttons); // Get the folded buttons @@ -385,6 +385,13 @@ var FormRenderer = BasicRenderer.extend({ this._registerModifiers(node, this.state, $result); return $result; }, + /** + * @private + * @returns {integer} + */ + _renderButtonBoxNbButtons: function () { + return [2, 2, 4, 6][config.device.size_class] || 7; + }, /** * @private * @param {Object} node 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 1f00b726630..ff1b8cb27d0 100644 --- a/addons/web/static/src/js/views/pivot/pivot_model.js +++ b/addons/web/static/src/js/views/pivot/pivot_model.js @@ -291,12 +291,10 @@ var PivotModel = AbstractModel.extend({ // if we don't have the key 'groupBy' in params. In that case, // we want to have the full open state for the groupbys. self._updateTree(old_row.root, self.data.main_row.root); - self._updateMainGroupBys(old_row, self.data.main_row); } if (!('pivot_column_groupby' in (params.context || {}))) { self._updateTree(old_col.root, self.data.main_col.root); - self._updateMainGroupBys(old_col, self.data.main_col); } }); }, @@ -747,19 +745,6 @@ var PivotModel = AbstractModel.extend({ this._traverseTree(root.children[i], f, arg1, arg2, arg3); } }, - /** - * Updates the groupBys that the main[row | col] holds - * - * @private - * @param {Object} old: The main abstract header before reload - * @param {Object} main: The main abstract header after reload - * - */ - _updateMainGroupBys: function (old, main) { - var new_groupby_length = this._getHeaderDepth(main.root) - 1; - main.root.groupbys = old.root.groupbys; - main.root.groupbys.splice(new_groupby_length); - }, /** * @param {Object} old_tree * @param {Object} new_tree diff --git a/addons/web/static/tests/views/pivot_tests.js b/addons/web/static/tests/views/pivot_tests.js index 41b4698ccd4..9ab5294a3a5 100644 --- a/addons/web/static/tests/views/pivot_tests.js +++ b/addons/web/static/tests/views/pivot_tests.js @@ -1036,6 +1036,41 @@ QUnit.module('Views', { pivot.destroy(); }); + QUnit.test('Empty results keep groupbys', function (assert) { + assert.expect(2); + + var pivot = createView({ + View: PivotView, + model: "partner", + data: this.data, + arch: '', + }); + + // Set a column groupby + pivot.$('thead .o_pivot_header_cell_closed').click(); + pivot.$('.o_field_selection li[data-field=customer] a').click(); + + // Set a domain for empty results + pivot.update({domain: [['id', '=', false]]}); + + var expectedContext = {pivot_column_groupby: undefined, + pivot_measures: undefined, + pivot_row_groupby: undefined}; + assert.deepEqual(pivot.getContext(), expectedContext, + 'Column groupby not lost after empty results'); + + // Set a domain for not empty results + pivot.update({domain: [['product_id', '=', 37]]}); + + var expectedContext = {pivot_column_groupby: ['customer'], + pivot_measures: ['__count'], + pivot_row_groupby: []}; + assert.deepEqual(pivot.getContext(), expectedContext, + 'Column groupby not lost after reload after empty results'); + + pivot.destroy(); + }); + QUnit.test('correctly uses pivot_ keys from the context', function (assert) { assert.expect(7); diff --git a/odoo/addons/base/models/res_partner.py b/odoo/addons/base/models/res_partner.py index c36b34603cc..d218095f3c6 100644 --- a/odoo/addons/base/models/res_partner.py +++ b/odoo/addons/base/models/res_partner.py @@ -426,7 +426,7 @@ class Partner(models.Model): as if they were related fields """ commercial_partner = self.commercial_partner_id if commercial_partner != self: - sync_vals = commercial_partner._update_fields_values(self._commercial_fields()) + sync_vals = commercial_partner.with_prefetch()._update_fields_values(self._commercial_fields()) self.write(sync_vals) @api.multi