From c0dad1ed4daebb8e2a938388dca7a3f81460bdd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Fri, 28 Jun 2019 10:40:32 +0000 Subject: [PATCH 01/13] [FIX] mail: fix computation of needaction for partners coming from channels This is mainly a typo / wrong computation introduced at 581a74143e9d215654b1988e2480c6516a3a4887. This commit also fixes some typos in comments. closes odoo/odoo#34443 Signed-off-by: Thibault Delavallee (tde) --- addons/mail/models/mail_thread.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index e419c5430b8..2a84d3b4d96 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -1996,15 +1996,15 @@ class MailThread(models.AbstractModel): if rdata['channels']: message_values['channel_ids'] = [(6, 0, [r['id'] for r in rdata['channels']])] if rdata['partners']: - message_values['needaction_partner_ids'] = [(6, 0, [r['id'] for r in rdata['partners'] if rdata['partners'] != 'channel_email'])] - # change of behaviour to check: since email_cids partner are added in _notify_compute_recipients, - # they will be added to needaction_partner_ids to. + message_values['needaction_partner_ids'] = [(6, 0, [r['id'] for r in rdata['partners'] if r['type'] != 'channel_email'])] + # change of behavior to check: since email_cids partner are added in _notify_compute_recipients, + # they will be added to needaction_partner_ids to. # we may want to filter them (example with channel_email, a cleaner solution may be great) - # -> instead of using _notify_customize_recipients, we could add a flag on rdata + # -> instead of using _notify_customize_recipients, we could add a flag on rdata # (would work for needactions, not if we want to erase partner_ids, ids) - # (could also be interresting for, we could add partners with r['notif'] = 'ocn_client' and r['needaction']=False) - # then overide a notify_recipients (as it was before) to effectively send ocn notifications. - # enveloppe will contain more needaction, those for the member of a email channel. + # (could also be interesting for, we could add partners with r['notif'] = 'ocn_client' and r['needaction']=False) + # then override a notify_recipients (as it was before) to effectively send ocn notifications. + # envelope will contain more needaction, those for the member of a email channel. if message_values and self: message_values.update(self._notify_customize_recipients(message, msg_vals)) if message_values: @@ -2252,9 +2252,10 @@ class MailThread(models.AbstractModel): email_cids = [r['id'] for r in recipient_data['channels'] if r['notif'] == 'email'] if email_cids: # we are doing a similar search in ocn_client - # Could be interresting to make everything in a single query. + # Could be interesting to make everything in a single query. # ocn_client: (searching all partners linked to channels of type chat). # here : (searching all partners linked to channels with notif email if email is not the author one) + # TDE FIXME: use email_sanitized email_from = msg_vals.get('email_from') or message.email_from exept_partner = [r['id'] for r in recipient_data['partners']] if author_id: From 569bbd7c22094491b183b2b8ad8a7c29a1bbf750 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Mon, 24 Jun 2019 13:44:16 +0000 Subject: [PATCH 02/13] [FIX] models: onchange handles inconsistent data in all cases closes odoo/odoo#34451 Signed-off-by: Raphael Collet (rco) --- odoo/models.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/odoo/models.py b/odoo/models.py index a96e7d88dab..591a237565c 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -5565,8 +5565,8 @@ Fields: for subname in subnames: new_lines.mapped(subname) - # Isolate changed x2many values, to handle inconsistent data sent from - # the client side: when a form view contains two one2many fields that + # Isolate changed values, to handle inconsistent data sent from the + # client side: when a form view contains two one2many fields that # overlap, the lines that appear in both fields may be sent with # different data. Consider, for instance: # @@ -5581,22 +5581,26 @@ Fields: # The idea is to put 'foo_ids' in cache first, so that the snapshot # contains value=1 for line in 'foo_ids'. The snapshot is then updated # with the value of `bar_ids`, which will contain value=2 on line. - values = dict(values) - changed_x2many = { - name: values.pop(name, []) - for name in names - if self._fields[name].type in ('one2many', 'many2many') - } + # + # The issue also occurs with other fields. For instance, an onchange on + # a move line has a value for the field 'move_id' that contains the + # values of the move, among which the one2many that contains the line + # itself, with old values! + # + changed_values = {name: values[name] for name in names} + # set changed values to null in initial_values; not setting them + # triggers default_get() on the new record when creating snapshot0 + initial_values = dict(values, **dict.fromkeys(names, False)) - # create a new record with values, and attach ``self`` to it - record = self.new(values, origin=self) + # create a new record with values + record = self.new(initial_values, origin=self) # make a snapshot based on the initial values of record snapshot0 = Snapshot(record, nametree) # store changed values in cache, and update snapshot0 - for name, value in changed_x2many.items(): - record._update_cache({name: value}, validate=False) + record._update_cache(changed_values, validate=False) + for name in names: snapshot0.fetch(name) # determine which field(s) should be triggered an onchange From c78960de887892962d29ecff59b00e4349f35fcb Mon Sep 17 00:00:00 2001 From: Yannick Tivisse Date: Mon, 1 Jul 2019 12:13:53 +0000 Subject: [PATCH 03/13] [FIX] base: Allow to create an user Now that an onchange set its result into the cache, we cannot create a user because the onchange tries to put the reified fields (eg: sel_4_7) into the cache. As those field don't exist and are pseudo-fields used to manage groups into the res user form view, we should replace the values by ORM commands on the groups_id field, as this is already the case in the create and the write methods. closes odoo/odoo#34487 Signed-off-by: Yannick Tivisse (yti) --- odoo/addons/base/models/res_users.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py index 6037c59d7d6..0d75d16e756 100644 --- a/odoo/addons/base/models/res_users.py +++ b/odoo/addons/base/models/res_users.py @@ -1195,6 +1195,18 @@ class UsersView(models.Model): user.write({'groups_id': [(4, group_multi_company.id)]}) return res + @api.model + def new(self, values={}, origin=None, ref=None): + values = self._remove_reified_groups(values) + user = super().new(values=values, origin=origin, ref=ref) + group_multi_company = self.env.ref('base.group_multi_company', False) + if group_multi_company and 'company_ids' in values: + if len(user.company_ids) <= 1 and user.id in group_multi_company.users.ids: + user.update({'groups_id': [(3, group_multi_company.id)]}) + elif len(user.company_ids) > 1 and user.id not in group_multi_company.users.ids: + user.update({'groups_id': [(4, group_multi_company.id)]}) + return user + def _remove_reified_groups(self, values): """ return `values` without reified group fields """ add, rem = [], [] From f72a0163891efa876d3b21c181d02c7be992ceb1 Mon Sep 17 00:00:00 2001 From: Pierre Masereel Date: Fri, 28 Jun 2019 13:59:48 +0000 Subject: [PATCH 04/13] [FIX] pos_hr: barcode and pin code access rights Since groups added on fields 'barcode' and 'pin' on hr employee in rev: c9ca3761464413327d2beb697553a3ccd7eef4d1 It is not possible anymore to launch the POS when 'pos_hr' module is installed. Because we don't want to allow users to access in read on all pin codes and barcodes of employees. We've made a function that will get a sha1 signature of the barcode and pin code of all employees to store it in the frontend of POS. We don't have other solutions concerning the security if we want to keep the POS working offline. We also know that it is not brute force proof, as pin code are composed of only digits and you can easily generate the signatures of all pincodes. closes odoo/odoo#34447 Signed-off-by: pimodoo --- addons/point_of_sale/static/lib/sha1.js | 159 +++++++++++++++++++ addons/point_of_sale/views/point_of_sale.xml | 1 + addons/pos_hr/models/__init__.py | 1 + addons/pos_hr/models/hr_employee.py | 22 +++ addons/pos_hr/static/src/js/gui.js | 2 +- addons/pos_hr/static/src/js/models.js | 28 +++- addons/pos_hr/static/src/js/screens.js | 2 +- 7 files changed, 212 insertions(+), 3 deletions(-) create mode 100644 addons/point_of_sale/static/lib/sha1.js create mode 100644 addons/pos_hr/models/hr_employee.py diff --git a/addons/point_of_sale/static/lib/sha1.js b/addons/point_of_sale/static/lib/sha1.js new file mode 100644 index 00000000000..0fc324cfd33 --- /dev/null +++ b/addons/point_of_sale/static/lib/sha1.js @@ -0,0 +1,159 @@ +/* from http://www.movable-type.co.uk/scripts/sha1.html */ +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* SHA-1 implementation in JavaScript (c) Chris Veness 2002-2014 / MIT Licence */ +/* */ +/* - see http://csrc.nist.gov/groups/ST/toolkit/secure_hashing.html */ +/* http://csrc.nist.gov/groups/ST/toolkit/examples.html */ +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* jshint node:true *//* global define, escape, unescape */ +'use strict'; + + +/** + * SHA-1 hash function reference implementation. + * + * @namespace + */ +var Sha1 = {}; + + +/** + * Generates SHA-1 hash of string. + * + * @param {string} msg - (Unicode) string to be hashed. + * @returns {string} Hash of msg as hex character string. + */ +Sha1.hash = function(msg) { + // convert string to UTF-8, as SHA only deals with byte-streams + msg = msg.utf8Encode(); + + // constants [§4.2.1] + var K = [ 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6 ]; + + // PREPROCESSING + + msg += String.fromCharCode(0x80); // add trailing '1' bit (+ 0's padding) to string [§5.1.1] + + // convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1] + var l = msg.length/4 + 2; // length (in 32-bit integers) of msg + ‘1’ + appended length + var N = Math.ceil(l/16); // number of 16-integer-blocks required to hold 'l' ints + var M = new Array(N); + + for (var i=0; i>> 32, but since JS converts + // bitwise-op args to 32 bits, we need to simulate this by arithmetic operators + M[N-1][14] = ((msg.length-1)*8) / Math.pow(2, 32); M[N-1][14] = Math.floor(M[N-1][14]); + M[N-1][15] = ((msg.length-1)*8) & 0xffffffff; + + // set initial hash value [§5.3.1] + var H0 = 0x67452301; + var H1 = 0xefcdab89; + var H2 = 0x98badcfe; + var H3 = 0x10325476; + var H4 = 0xc3d2e1f0; + + // HASH COMPUTATION [§6.1.2] + + var W = new Array(80); var a, b, c, d, e; + for (var i=0; i>>(32-n)); +}; + + +/** + * Hexadecimal representation of a number. + * @private + */ +Sha1.toHexStr = function(n) { + // note can't use toString(16) as it is implementation-dependant, + // and in IE returns signed numbers when used on full words + var s="", v; + for (var i=7; i>=0; i--) { v = (n>>>(i*4)) & 0xf; s += v.toString(16); } + return s; +}; + + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + + +/** Extend String object with method to encode multi-byte string to utf8 + * - monsur.hossa.in/2012/07/20/utf-8-in-javascript.html */ +if (typeof String.prototype.utf8Encode == 'undefined') { + String.prototype.utf8Encode = function() { + return unescape( encodeURIComponent( this ) ); + }; +} + +/** Extend String object with method to decode utf8 string to multi-byte */ +if (typeof String.prototype.utf8Decode == 'undefined') { + String.prototype.utf8Decode = function() { + try { + return decodeURIComponent( escape( this ) ); + } catch (e) { + return this; // invalid UTF-8? return as-is + } + }; +} + + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +if (typeof module != 'undefined' && module.exports) module.exports = Sha1; // CommonJs export +if (typeof define == 'function' && define.amd) define([], function() { return Sha1; }); // AMD diff --git a/addons/point_of_sale/views/point_of_sale.xml b/addons/point_of_sale/views/point_of_sale.xml index b476328700a..1d8cc009793 100644 --- a/addons/point_of_sale/views/point_of_sale.xml +++ b/addons/point_of_sale/views/point_of_sale.xml @@ -10,6 +10,7 @@ + diff --git a/addons/pos_hr/models/__init__.py b/addons/pos_hr/models/__init__.py index 5e5c6fc9b07..784da4cda9e 100644 --- a/addons/pos_hr/models/__init__.py +++ b/addons/pos_hr/models/__init__.py @@ -2,3 +2,4 @@ from . import pos_config from . import pos_order +from . import hr_employee diff --git a/addons/pos_hr/models/hr_employee.py b/addons/pos_hr/models/hr_employee.py new file mode 100644 index 00000000000..fc5a3f9b154 --- /dev/null +++ b/addons/pos_hr/models/hr_employee.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import hashlib + +from odoo import api, models + +class HrEmployee(models.Model): + + _inherit = 'hr.employee' + + def get_barcodes_and_pin_hashed(self): + if not self.env.user.has_group('point_of_sale.group_pos_user'): + return [] + # Apply visibility filters (record rules) + visible_emp_ids = self.search([('id', 'in', self.ids)]) + employees_data = self.sudo().search_read([('id', 'in', visible_emp_ids.ids)], ['barcode', 'pin']) + + for e in employees_data: + e['barcode'] = hashlib.sha1(e['barcode'].encode('utf8')).hexdigest() if e['barcode'] else False + e['pin'] = hashlib.sha1(e['pin'].encode('utf8')).hexdigest() if e['pin'] else False + return employees_data diff --git a/addons/pos_hr/static/src/js/gui.js b/addons/pos_hr/static/src/js/gui.js index b0faa322924..45c20b7551e 100644 --- a/addons/pos_hr/static/src/js/gui.js +++ b/addons/pos_hr/static/src/js/gui.js @@ -56,7 +56,7 @@ gui.Gui.include({ self.show_popup('password',{ 'title': _t('Password ?'), confirm: function (pw) { - if (pw !== password) { + if (Sha1.hash(pw) !== password) { self.show_popup('error', _t('Incorrect Password')); reject(); } else { diff --git a/addons/pos_hr/static/src/js/models.js b/addons/pos_hr/static/src/js/models.js index 54a6b1d31d1..4d9e0e20e23 100644 --- a/addons/pos_hr/static/src/js/models.js +++ b/addons/pos_hr/static/src/js/models.js @@ -2,6 +2,7 @@ odoo.define('pos_hr.employees', function (require) { "use strict"; var models = require('point_of_sale.models'); +var rpc = require('web.rpc'); models.load_models([{ model: 'res.users', @@ -27,7 +28,7 @@ models.load_models([{ }, },{ model: 'hr.employee', - fields: ['name', 'id', 'barcode', 'pin', 'user_id'], + fields: ['name', 'id', 'user_id'], domain: function(self){ return [['company_id', '=', self.config.company_id[0]]]; }, loaded: function(self, employees) { if (self.config.module_pos_hr) { @@ -53,4 +54,29 @@ models.load_models([{ } } }]); + +var posmodel_super = models.PosModel.prototype; +models.PosModel = models.PosModel.extend({ + load_server_data: function () { + var self = this; + return posmodel_super.load_server_data.apply(this, arguments).then(function () { + var employee_ids = _.map(self.employees, function(employee){return employee.id;}); + var records = rpc.query({ + model: 'hr.employee', + method: 'get_barcodes_and_pin_hashed', + args: [employee_ids], + }); + return records.then(function (employee_data) { + self.employees.forEach(function (employee) { + var data = _.findWhere(employee_data, {'id': employee.id}); + if (data !== undefined){ + employee.barcode = data.barcode; + employee.pin = data.pin; + } + }); + }); + }); + }, +}); + }); diff --git a/addons/pos_hr/static/src/js/screens.js b/addons/pos_hr/static/src/js/screens.js index 9b7fe1c4a9a..bef83516f0f 100644 --- a/addons/pos_hr/static/src/js/screens.js +++ b/addons/pos_hr/static/src/js/screens.js @@ -17,7 +17,7 @@ ScreenWidget.include({ var self = this; var employees = this.pos.employees; for(var i = 0, len = employees.length; i < len; i++){ - if(employees[i].barcode === code.code){ + if(employees[i].barcode === Sha1.hash(code.code)){ if (employees[i].id !== this.pos.get_cashier().id && employees[i].pin) { return this.gui.ask_password(employees[i].pin).then(function(){ self.pos.set_cashier(employees[i]); From e09a5fb3ea29b50ae2c348fd4aefdaf4ab77aebf Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 2 Jul 2019 09:49:18 +0200 Subject: [PATCH 05/13] [FIX] base: don't overwrite existing extensions The point was to fix files attachments without an extension, potentially appending to existing extensions was not a smart idea. See #34299, 5eb15c01b556620c77c0f7327494d4bf3117d115 --- odoo/addons/base/models/ir_http.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/odoo/addons/base/models/ir_http.py b/odoo/addons/base/models/ir_http.py index 897ca1de9ba..36ec15a40ae 100644 --- a/odoo/addons/base/models/ir_http.py +++ b/odoo/addons/base/models/ir_http.py @@ -367,9 +367,11 @@ class IrHttp(models.AbstractModel): mimetype = guess_mimetype(base64.b64decode(content), default=default_mimetype) # extension - extension = mimetypes.guess_extension(mimetype) - if extension and not filename.endswith(extension): - filename = "%s%s" % (filename, extension) + _, existing_extension = os.path.splitext(filename) + if not existing_extension: + extension = mimetypes.guess_extension(mimetype) + if extension: + filename = "%s%s" % (filename, extension) if not filehash: filehash = '"%s"' % hashlib.md5(pycompat.to_text(content).encode('utf-8')).hexdigest() From a766f5b7c19d6387a946b1556c59219d95767771 Mon Sep 17 00:00:00 2001 From: jem-odoo Date: Tue, 25 Jun 2019 07:05:36 +0000 Subject: [PATCH 06/13] [REV] uom: introduces duration unit of measure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 43d51466c858719736c3d2be0c9ef5b4f6244733. Reasons: - too complex having 2 UoM "Hours" - impossible with UoM to modelise month: is that 30 days or 31 ? Task-1971491 closes odoo/odoo#34497 Signed-off-by: Jérome Maes (jem) --- addons/uom/data/uom_data.xml | 23 ----------------------- addons/uom/models/uom_uom.py | 3 +-- 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/addons/uom/data/uom_data.xml b/addons/uom/data/uom_data.xml index bece7ca77d9..e46a7de9c35 100644 --- a/addons/uom/data/uom_data.xml +++ b/addons/uom/data/uom_data.xml @@ -16,10 +16,6 @@ Working Time working_time - - Time - time - Length / Distance length @@ -56,25 +52,6 @@ smaller - - - Day(s) - - - - - Week(s) - - - bigger - - - Hour(s) - - - smaller - - Day(s) diff --git a/addons/uom/models/uom_uom.py b/addons/uom/models/uom_uom.py index 6907a9fb890..95d7219e052 100644 --- a/addons/uom/models/uom_uom.py +++ b/addons/uom/models/uom_uom.py @@ -13,7 +13,6 @@ class UoMCategory(models.Model): measure_type = fields.Selection([ ('unit', 'Units'), ('weight', 'Weight'), - ('time', 'Time (Duration)'), ('working_time', 'Working Time'), ('length', 'Length'), ('volume', 'Volume'), @@ -25,7 +24,7 @@ class UoMCategory(models.Model): @api.multi def unlink(self): - if self.filtered(lambda categ: categ.measure_type in ['working_time', 'time']): + if self.filtered(lambda categ: categ.measure_type == 'working_time'): raise UserError(_("You cannot delete this UoM Category as it is used by the system.")) return super(UoMCategory, self).unlink() From ba7675210f1cc71cfe47a062388c39fce8c9cec0 Mon Sep 17 00:00:00 2001 From: Cedric Snauwaert Date: Thu, 27 Jun 2019 12:17:59 +0000 Subject: [PATCH 07/13] [FIX] web: two o2m fields with onchanges Scenario: We have the following models: class A: submodel_1_ids = fields.One2Many(B) submodel_2_ids = fields.One2Many(B) class B: parent_model = fields.many2one(A) name = fields.Char() and the following onchange on class A: api.onchange(submodel_1_ids) def onchange_submodel1(self): // add newline to second o2m submodel_2_ids = submodel_1_ids api.onchange(submodel_2_ids) def onchange_submodel2(self): do_smth In a form view containing both o2m fields (editable="bottom" lists): 1) add a line on first o2m -> the onchange adds the same line on the other o2m 2) add a line on the second o2m Before this rev., the line (added in (2)) wasn't added at the bottom of the list, and the row in edition wasn't the newly created one. The issue occurred because we couldn't retrieve the datapoint corresponding to the other line (the one created by the onchange on the first o2m), and we thus created another which was put at the end of the list. Then, the BasicModel messed up the sort of the records because it couldn't match the virtual id of that new record with the ones in orderedResIds. We fixed the issue by preventing the model from creating a new datapoint after the onchange, by using 'ref' instead of 'res_id' to retrieve the one that already exist. closes odoo/odoo#34493 Signed-off-by: VincentSchippefilt Co-authored-by: Aaron Bohy Co-authored-by: David Monjoie --- .../static/src/js/views/basic/basic_model.js | 2 +- .../relational_fields/field_one2many_tests.js | 69 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js index 0cddc887f18..60c2f6a3314 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -1597,7 +1597,7 @@ var BasicModel = AbstractModel.extend({ // updating an existing (virtual) record var previousChange = _.find(oldChanges, function (operation) { var child = self.localData[operation.id]; - return child && (child.res_id === command[1]); + return child && (child.ref === command[1]); }); recID = previousChange && previousChange.id; rec = self.localData[recID]; diff --git a/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js b/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js index 7f19dc59d59..fda57b6d4dc 100644 --- a/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js +++ b/addons/web/static/tests/fields/relational_fields/field_one2many_tests.js @@ -8397,6 +8397,75 @@ QUnit.module('fields', {}, function () { form.destroy(); }); + + QUnit.test('two one2many fields with same relation and onchanges', async function (assert) { + // this test simulates the presence of two one2many fields with onchanges, such that + // changes to the first o2m are repercuted on the second one + assert.expect(6); + + this.data.partner.fields.turtles2 = { + string: "Turtles 2", + type: "one2many", + relation: 'turtle', + relation_field: 'turtle_trululu', + }; + this.data.partner.onchanges = { + turtles: function (obj) { + // when we add a line to turtles, add same line to turtles2 + if (obj.turtles.length) { + obj.turtles = [[5]].concat(obj.turtles); + obj.turtles2 = obj.turtles; + } + }, + turtles2: function (obj) { + // simulate an onchange on turtles2 as well + if (obj.turtles2.length) { + obj.turtles2 = [[5]].concat(obj.turtles2); + } + } + }; + + var form = await createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '
', + }); + + // trigger first onchange by adding a line in turtles field (should add a line in turtles2) + await testUtils.dom.click(form.$('.o_field_widget[name="turtles"] .o_field_x2many_list_row_add a')); + await testUtils.fields.editInput(form.$('.o_field_widget[name="turtles"] .o_field_widget[name="name"]'), 'ABC'); + + assert.containsOnce(form, '.o_field_widget[name="turtles"] .o_data_row', + 'line of first o2m should have been created'); + assert.containsOnce(form, '.o_field_widget[name="turtles2"] .o_data_row', + 'line of second o2m should have been created'); + + // add a line in turtles2 + await testUtils.dom.click(form.$('.o_field_widget[name="turtles2"] .o_field_x2many_list_row_add a')); + await testUtils.fields.editInput(form.$('.o_field_widget[name="turtles2"] .o_field_widget[name="name"]'), 'DEF'); + + assert.containsOnce(form, '.o_field_widget[name="turtles"] .o_data_row', + 'we should still have 1 line in turtles'); + assert.containsN(form, '.o_field_widget[name="turtles2"] .o_data_row', 2, + 'we should have 2 lines in turtles2'); + assert.hasClass(form.$('.o_field_widget[name="turtles2"] .o_data_row:nth(1)'), 'o_selected_row', + 'second row should be in edition'); + + await testUtils.form.clickSave(form); + + assert.strictEqual(form.$('.o_field_widget[name="turtles2"] .o_data_row').text(), 'ABCDEF'); + + form.destroy(); + }); + }); }); }); From 9182266222f4424819f575c0a487b55b9709fe87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 3 Jul 2019 11:13:43 +0000 Subject: [PATCH 08/13] [FIX] crm: add forgotten mixin for phone validation Commit f60825b43979345b456510db25f70264a93cbdb0 moved phone validation addition on res.partner model from a specific module to crm directly. However the inherit from the validation mixin class was forgotten, leading to a crash when changing phone from the UI. closes odoo/odoo#34545 Signed-off-by: Thibault Delavallee (tde) --- addons/crm/models/res_partner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/crm/models/res_partner.py b/addons/crm/models/res_partner.py index 55747d8d9b4..b6b52b95c11 100644 --- a/addons/crm/models/res_partner.py +++ b/addons/crm/models/res_partner.py @@ -5,8 +5,8 @@ from odoo import api, fields, models class Partner(models.Model): - - _inherit = 'res.partner' + _name = 'res.partner' + _inherit = ['res.partner', 'phone.validation.mixin'] team_id = fields.Many2one('crm.team', string='Sales Team', oldname='section_id') opportunity_ids = fields.One2many('crm.lead', 'partner_id', string='Opportunities', domain=[('type', '=', 'opportunity')]) From 2337d4115bc53fd8be574480dcf432283432bd78 Mon Sep 17 00:00:00 2001 From: qdp-odoo Date: Wed, 3 Jul 2019 21:47:55 +0000 Subject: [PATCH 09/13] [FIX] sale: traceback in sales team dashboard * Sales > Orders > Sales Team > traceback with error 'user_type_id' isn't defined on account.move.line. * Also the move.team_id has to appear in some group by because we're using an aggregate function. Both bugs have been introduced by beaa30a (aka accounting-pocalypse) closes odoo/odoo#34570 Signed-off-by: Quentin De Paoli (qdp) --- addons/sale/models/sales_team.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/sale/models/sales_team.py b/addons/sale/models/sales_team.py index 280b6d0a3ad..d0a16ecc1b7 100644 --- a/addons/sale/models/sales_team.py +++ b/addons/sale/models/sales_team.py @@ -74,7 +74,6 @@ class CrmTeam(models.Model): SUM(line.balance) AS amount_untaxed_signed FROM account_move move LEFT JOIN account_move_line line ON line.move_id = move.id - INNER JOIN account_account_type account_type ON account_type.id = line.user_type_id WHERE move.type IN ('out_invoice', 'out_refund', 'in_invoice', 'in_refund') AND move.invoice_payment_state IN ('in_payment', 'paid') AND move.state = 'posted' @@ -82,7 +81,8 @@ class CrmTeam(models.Model): AND move.date BETWEEN %s AND %s AND line.tax_line_id IS NULL AND line.display_type IS NULL - AND account_type.type NOT IN ('receivable', 'payable') + AND line.account_internal_type NOT IN ('receivable', 'payable') + GROUP BY move.team_id ''' today = fields.Date.today() params = [tuple(self.ids), fields.Date.to_string(today), fields.Date.to_string(today.replace(day=1))] From e102ca85466da2cfccdabf53f1109c451691440f Mon Sep 17 00:00:00 2001 From: fja-odoo Date: Wed, 26 Jun 2019 11:44:25 +0000 Subject: [PATCH 10/13] [FIX] website_sale: fix open_website_url for product open_website_url was targeting product_template website_url instead of product_product. Related to https://github.com/odoo/odoo/pull/33175 closes odoo/odoo#34383 Signed-off-by: Quentin Smetz (qsm) --- addons/website_sale/models/product.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/addons/website_sale/models/product.py b/addons/website_sale/models/product.py index e5bf6ca82f4..a0909186eec 100644 --- a/addons/website_sale/models/product.py +++ b/addons/website_sale/models/product.py @@ -448,6 +448,13 @@ class Product(models.Model): res['url'] = self.website_url return res + @api.multi + def open_website_url(self): + self.ensure_one() + res = self.product_tmpl_id.open_website_url() + res['url'] = self.website_url + return res + @api.multi def _get_images(self): """Return a list of records implementing `image.mixin` to From e6c23a45def59d56343d963885bd1df0efdf1e17 Mon Sep 17 00:00:00 2001 From: Christophe Monniez Date: Thu, 4 Jul 2019 11:23:31 +0000 Subject: [PATCH 11/13] [FIX] packaging: oversight of polib in setup.py Since commit 632fa044c3c, polib is used instead of a custom pofile reader. The setup.py file was forgotten in packaging, leading to a faulty source package. closes odoo/odoo#34585 Signed-off-by: Christophe Monniez (moc) --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index c9531cac4b3..00d90191525 100644 --- a/setup.py +++ b/setup.py @@ -37,6 +37,7 @@ setup( 'ofxparse', 'passlib', 'pillow', # windows binary http://www.lfd.uci.edu/~gohlke/pythonlibs/ + 'polib', 'psutil', # windows binary code.google.com/p/psutil/downloads/list 'psycopg2 >= 2.2', 'pydot', From 1f5e89b176080dcf3434eb823a306552d53b261e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 8 Jul 2019 09:59:59 +0000 Subject: [PATCH 12/13] [FIX] mail: restore send after commit behavior for notification emails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously to commit [1] notification emails were send using the after commit hook (see [2] for related code move and [3] for original code). That commit changed the parameter default value without any reason, meaning this mechanism is not used anymore. This commit fixes that behavior. [1] See commit 581a74143e9d215654b1988e2480c6516a3a4887: code cleaning [2] See commit 7a6269719fb8663dd62e6313a04a549283989541: code move [3] See commit 38e181ee2c858decbf8feb935f59019591506621: use of post-commit hook in mail closes odoo/odoo#34645 Signed-off-by: Xavier Dollé (xdo) --- addons/mail/models/mail_thread.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index 2a84d3b4d96..0a3743969f1 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -2029,7 +2029,7 @@ class MailThread(models.AbstractModel): self.env['bus.bus'].sudo().sendmany(notifications) return True - def _notify_record_by_email(self, message, partners_data, msg_vals=False, model_description=False, mail_auto_delete=True, send_after_commit=False): + def _notify_record_by_email(self, message, partners_data, msg_vals=False, model_description=False, mail_auto_delete=True, send_after_commit=True): """ Method to send email linked to notified messages. :param message: mail.message record to notify; :param partners_data: partner to notify by email coming from _notify_compute_recipients From b27c145cf2f95e00d7155db99a08f631e1a21bc4 Mon Sep 17 00:00:00 2001 From: Kevin Baptiste Date: Tue, 2 Jul 2019 07:41:01 +0000 Subject: [PATCH 13/13] [IMP] website_hr: remove read ACL from portal user The read permission on `hr.employee` has been removed from the public and portal users; replaced with read access on the public employee profile. closes odoo/odoo#34508 Signed-off-by: Yannick Tivisse (yti) --- addons/website_hr/security/hr_employee_security.xml | 4 ++-- addons/website_hr/security/ir.model.access.csv | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/website_hr/security/hr_employee_security.xml b/addons/website_hr/security/hr_employee_security.xml index a86b14310a8..8cdbaebb846 100644 --- a/addons/website_hr/security/hr_employee_security.xml +++ b/addons/website_hr/security/hr_employee_security.xml @@ -2,7 +2,7 @@ hr_employee: Public - + [('website_published', '=', True)] @@ -12,7 +12,7 @@ hr_employee: Portal - + [('website_published', '=', True)] diff --git a/addons/website_hr/security/ir.model.access.csv b/addons/website_hr/security/ir.model.access.csv index 53be505e42e..787e73ce178 100644 --- a/addons/website_hr/security/ir.model.access.csv +++ b/addons/website_hr/security/ir.model.access.csv @@ -1,5 +1,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_hr_employee_public,hr.employee.public,hr.model_hr_employee,base.group_public,1,0,0,0 -access_hr_employee_portal,hr.employee.portal,hr.model_hr_employee,base.group_portal,1,0,0,0 +access_hr_employee_public,hr.employee.public,hr.model_hr_employee_public,base.group_public,1,0,0,0 +access_hr_employee_portal,hr.employee.portal,hr.model_hr_employee_public,base.group_portal,1,0,0,0 access_resource_public,resource.resource.public,resource.model_resource_resource,base.group_public,1,0,0,0 access_resource_portal,resource.resource.portal,resource.model_resource_resource,base.group_portal,1,0,0,0