diff --git a/addons/hr_attendance/static/src/js/kiosk_mode.js b/addons/hr_attendance/static/src/js/kiosk_mode.js index d561f380c45..234df40fe07 100644 --- a/addons/hr_attendance/static/src/js/kiosk_mode.js +++ b/addons/hr_attendance/static/src/js/kiosk_mode.js @@ -1,6 +1,7 @@ odoo.define('hr_attendance.kiosk_mode', function (require) { "use strict"; +var ajax = require('web.ajax'); var core = require('web.core'); var Widget = require('web.Widget'); var Session = require('web.session'); @@ -27,6 +28,8 @@ var KioskMode = Widget.extend({ self.$el.html(QWeb.render("HrAttendanceKioskMode", {widget: self})); self.start_clock(); }); + // Make a RPC call every day to keep the session alive + self._interval = window.setInterval(this._callServer.bind(this), (60*60*1000*24)); return $.when(def, this._super.apply(this, arguments)); }, @@ -55,8 +58,15 @@ var KioskMode = Widget.extend({ destroy: function () { core.bus.off('barcode_scanned', this, this._onBarcodeScanned); clearInterval(this.clock_start); + clearInterval(this._interval); this._super.apply(this, arguments); }, + + _callServer: function () { + // Make a call to the database to avoid the auto close of the session + return ajax.rpc("/web/webclient/version_info", {}); + }, + }); core.action_registry.add('hr_attendance_kiosk_mode', KioskMode); diff --git a/addons/iap/__manifest__.py b/addons/iap/__manifest__.py index 35124df76eb..6a30ed891c9 100644 --- a/addons/iap/__manifest__.py +++ b/addons/iap/__manifest__.py @@ -9,7 +9,7 @@ 'description': """ This module provides standard tools (account model, context manager and helpers) to support In-App purchases inside Odoo. """, - 'depends': ['web'], + 'depends': ['web', 'web_settings_dashboard'], 'data': [ 'security/ir.model.access.csv', 'security/ir_rule.xml', diff --git a/addons/purchase/report/purchase_report.py b/addons/purchase/report/purchase_report.py index 95ddfe49bc5..603dc9645f2 100644 --- a/addons/purchase/report/purchase_report.py +++ b/addons/purchase/report/purchase_report.py @@ -88,7 +88,7 @@ class PurchaseReport(models.Model): join res_partner partner on s.partner_id = partner.id left join product_product p on (l.product_id=p.id) left join product_template t on (p.product_tmpl_id=t.id) - LEFT JOIN ir_property ip ON (ip.name='standard_price' AND ip.res_id=CONCAT('product.template,',t.id) AND ip.company_id=s.company_id) + LEFT JOIN ir_property ip ON (ip.name='standard_price' AND ip.res_id=CONCAT('product.product,',p.id) AND ip.company_id=s.company_id) left join product_uom u on (u.id=l.product_uom) left join product_uom u2 on (u2.id=t.uom_id) left join stock_picking_type spt on (spt.id=s.picking_type_id) diff --git a/addons/web/static/src/js/views/abstract_controller.js b/addons/web/static/src/js/views/abstract_controller.js index ff7ab5a43bb..fbddb28eab7 100644 --- a/addons/web/static/src/js/views/abstract_controller.js +++ b/addons/web/static/src/js/views/abstract_controller.js @@ -12,6 +12,7 @@ odoo.define('web.AbstractController', function (require) { * reading localstorage, ...) has to go through the controller. */ +var concurrency = require('web.concurrency'); var ControlPanelMixin = require('web.ControlPanelMixin'); var core = require('web.core'); var Widget = require('web.Widget'); @@ -54,6 +55,8 @@ var AbstractController = Widget.extend(ControlPanelMixin, { this.controllerID = params.controllerID; this.initialState = params.initialState; + // use a DropPrevious to correctly handle concurrent updates + this.dp = new concurrency.DropPrevious(); // those arguments are temporary, they won't be necessary as soon as the // ControlPanel will be handled by the View this.displayName = params.displayName; @@ -225,11 +228,11 @@ var AbstractController = Widget.extend(ControlPanelMixin, { var self = this; var shouldReload = (options && 'reload' in options) ? options.reload : true; var def = shouldReload ? this.model.reload(this.handle, params) : $.when(); - return def.then(function (handle) { + return this.dp.add(def).then(function (handle) { self.handle = handle || self.handle; // update handle if we reloaded var state = self.model.get(self.handle); var localState = self.renderer.getLocalState(); - return self.renderer.updateState(state, params).then(function () { + return self.dp.add(self.renderer.updateState(state, params)).then(function () { self.renderer.setLocalState(localState); self._update(state); }); diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index e322f35abb4..fd851fc55de 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -3309,7 +3309,6 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('basic support for widgets', function (assert) { assert.expect(1); @@ -3415,6 +3414,46 @@ QUnit.module('Views', { testUtils.unpatch(mixins.ParentedMixin); }); + QUnit.test('concurrent reloads finishing in inverse order', function (assert) { + assert.expect(3); + + var blockSearchRead = false; + var def = $.Deferred(); + var list = createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '', + mockRPC: function (route) { + var result = this._super.apply(this, arguments); + if (route === '/web/dataset/search_read' && blockSearchRead) { + return $.when(def).then(_.constant(result)); + } + return result; + }, + }); + + assert.strictEqual(list.$('.o_list_view .o_data_row').length, 4, + "list view should contain 4 records"); + + // reload with a domain (this request is blocked) + blockSearchRead = true; + list.reload({domain: [['foo', '=', 'yop']]}); + + assert.strictEqual(list.$('.o_list_view .o_data_row').length, 4, + "list view should still contain 4 records (search_read being blocked)"); + + // reload without the domain + blockSearchRead = false; + list.reload({domain: []}); + + // unblock the RPC + def.resolve(); + assert.strictEqual(list.$('.o_list_view .o_data_row').length, 4, + "list view should still contain 4 records"); + + list.destroy(); + }); }); }); diff --git a/addons/website_quote/__manifest__.py b/addons/website_quote/__manifest__.py index e6a0a1c274f..115f8f6821d 100644 --- a/addons/website_quote/__manifest__.py +++ b/addons/website_quote/__manifest__.py @@ -7,7 +7,7 @@ 'website': 'https://www.odoo.com/page/quote-builder', 'version': '1.0', 'description': "", - 'depends': ['website', 'sale_management', 'mail', 'payment', 'website_mail'], + 'depends': ['website', 'sale_management', 'mail', 'payment', 'website_mail', 'sale_payment'], 'data': [ 'data/website_quote_data.xml', 'report/sale_order_reports.xml', diff --git a/odoo/addons/base/models/res_partner.py b/odoo/addons/base/models/res_partner.py index 30a4cae01b5..a9a5b36fc2e 100644 --- a/odoo/addons/base/models/res_partner.py +++ b/odoo/addons/base/models/res_partner.py @@ -179,7 +179,9 @@ class Partner(models.Model): [('contact', 'Contact'), ('invoice', 'Invoice address'), ('delivery', 'Shipping address'), - ('other', 'Other address')], string='Address Type', + ('other', 'Other address'), + ("private", "Private Address"), + ], string='Address Type', default='contact', help="Used to select automatically the right address according to the context in sales and purchases documents.") street = fields.Char() diff --git a/odoo/addons/base/security/base_security.xml b/odoo/addons/base/security/base_security.xml index 8d03802aadf..f3bf2f908dd 100644 --- a/odoo/addons/base/security/base_security.xml +++ b/odoo/addons/base/security/base_security.xml @@ -1,5 +1,9 @@ + + Access to Private Addresses + + res.users.log per user @@ -69,6 +73,37 @@ ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])] + + + res.partner.rule.private.employee + + + [('type', '!=', 'private')] + + + + + + + + + + res.partner.rule.private.group + + + [('type', '=', 'private')] + + + + + + + + ir.filters.admin.all.rights diff --git a/odoo/addons/base/views/res_partner_views.xml b/odoo/addons/base/views/res_partner_views.xml index 8893ee1cb15..3eb69146c4c 100644 --- a/odoo/addons/base/views/res_partner_views.xml +++ b/odoo/addons/base/views/res_partner_views.xml @@ -390,6 +390,50 @@ + + + res.partner.view.form.private + res.partner + + +
+ + + +
+
+
+ res.partner.select res.partner