From 83e3254519faa217339969ee990322478cf2d5e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9rome=20Maes?= Date: Tue, 11 Jul 2017 14:38:21 +0200 Subject: [PATCH 01/33] [FIX] sale_timesheet: reactive tests Disabled by mistake. I'm a looser sometimes ... --- addons/sale_timesheet/tests/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/sale_timesheet/tests/__init__.py b/addons/sale_timesheet/tests/__init__.py index 502409b7144..9b3932ef5fb 100644 --- a/addons/sale_timesheet/tests/__init__.py +++ b/addons/sale_timesheet/tests/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -#from . import test_sale_timesheet -#from . import test_sale_service +from . import test_sale_timesheet +from . import test_sale_service from . import test_timesheet_revenue From 8881e61ec8512e504fcf798519745d73b68b5656 Mon Sep 17 00:00:00 2001 From: Martin Geubelle Date: Tue, 11 Jul 2017 17:18:19 +0200 Subject: [PATCH 02/33] [FIX] mail: make many2many_tags_email widget work This widget hasn't been rewritten with the new views. The purpose of this widget is to open a popup for each invalid partners (without email) in a many2many to fill the email. If the email is not set, the partner will not appear in the tags and its id will not be sent (which should not be the role of a widget but that's the way it is). --- .../static/src/js/many2many_tags_email.js | 146 ++++++++++++++---- addons/mail/static/tests/chatter_tests.js | 82 ++++++++++ .../static/src/js/views/basic/basic_model.js | 3 +- 3 files changed, 196 insertions(+), 35 deletions(-) diff --git a/addons/mail/static/src/js/many2many_tags_email.js b/addons/mail/static/src/js/many2many_tags_email.js index 52e213ddb7d..d266147bcc7 100644 --- a/addons/mail/static/src/js/many2many_tags_email.js +++ b/addons/mail/static/src/js/many2many_tags_email.js @@ -1,52 +1,130 @@ odoo.define('mail.many2manytags', function (require) { "use strict"; -// FIXME: apply https://github.com/odoo/odoo/commit/1217ae914b313df7fe8511c138871e585bb21c81 - +var BasicModel = require('web.BasicModel'); var core = require('web.core'); var form_common = require('web.view_dialogs'); var field_registry = require('web.field_registry'); var relational_fields = require('web.relational_fields'); +var M2MTags = relational_fields.FieldMany2ManyTags; var _t = core._t; -var FieldMany2ManyTags = relational_fields.FieldMany2ManyTags; +BasicModel.include({ -var FieldMany2ManyTagsEmail = FieldMany2ManyTags.extend({ - init: function() { - this.values_checking = []; - this._super.apply(this, arguments); - }, + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- - add_id: function (id) { + /** + * @private + * @param {Object} record - an element from the localData + * @param {string} fieldName + * @return {Deferred} the deferred is resolved with the + * invalidPartnerIds + */ + _setInvalidMany2ManyTagsEmail: function (record, fieldName) { var self = this; - var _super = this._super.bind(this); - // check partner has email - this.trigger_up('perform_model_rpc', { - model: 'res.partner', - method: 'search', - args: [[ - ["id", "=", id], - ["email", "=", false], - ]], - on_success: function(partner_id) { - if (partner_id.length) { - // invalid partner - var pop = new form_common.FormViewDialog(self, { - res_model: 'res.partner', - res_id: partner_id[0], - title: _t("Please complete partner's informations and Email"), - }).open(); - pop.on('write_completed', self, function () { - // self.values_checking = _.without(self.values_checking, id); - _super.apply(self, [id]); - }); - } else { - // valid partner - _super.apply(self, [id]); - } + var localID = (record._changes && fieldName in record._changes) ? + record._changes[fieldName] : + record.data[fieldName]; + var list = this.localData[localID]; + var invalidPartnerIds = []; + _.each(list.data, function (id) { + var record = self.localData[id]; + if (!record.data.email) { + invalidPartnerIds.push(record); } }); + var def; + if (invalidPartnerIds) { + // remove invalid partners + var changes = {operation: 'REMOVE', ids: _.pluck(invalidPartnerIds, 'id')}; + def = this._applyX2ManyChange(record, fieldName, changes); + } + return $.when(def).then(function () { + return $.when({ + invalidPartnerIds: _.pluck(invalidPartnerIds, 'res_id'), + }); + }); + }, +}); + +var FieldMany2ManyTagsEmail = M2MTags.extend({ + fieldsToFetch: _.extend({}, M2MTags.prototype.fieldsToFetch, { + email: {type: 'char'}, + }), + specialData: "_setInvalidMany2ManyTagsEmail", + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Open a popup for each invalid partners (without email) to fill the email. + * + * @private + * @returns {Deferred} + */ + _checkEmailPopup: function () { + var self = this; + + var popupDefs = []; + var validPartners = []; + + // propose the user to correct invalid partners + _.each(this.record.specialData[this.name].invalidPartnerIds, function (resID) { + var def = $.Deferred(); + popupDefs.push(def); + + var pop = new form_common.FormViewDialog(self, { + res_model: self.field.relation, + res_id: resID, + title: _t("Please complete partner's informations and Email"), + on_saved: function (record) { + if (record.data.email) { + validPartners.push(record.res_id); + } + }, + }).open(); + pop.on('closed', self, function () { + def.resolve(); + }); + }); + return $.when.apply($, popupDefs).then(function() { + // All popups have been processed for the given ids + // It is now time to set the final value with valid partners ids. + validPartners = _.uniq(validPartners); + if (validPartners.length) { + var values = _.map(validPartners, function (id) { + return {id: id}; + }); + self._setValue({ + operation: 'ADD_M2M', + ids: values, + }); + } + }); + }, + /** + * Override to check if all many2many values have an email set before + * rendering the widget. + * + * @override + * @private + */ + _render: function () { + var self = this; + var def = $.Deferred(); + var _super = this._super.bind(this); + if (this.record.specialData[this.name].invalidPartnerIds.length) { + def = this._checkEmailPopup(); + } else { + def.resolve(); + } + return def.then(function () { + return _super.apply(self, arguments); + }); }, }); diff --git a/addons/mail/static/tests/chatter_tests.js b/addons/mail/static/tests/chatter_tests.js index f55e247a5b6..030539a72b9 100644 --- a/addons/mail/static/tests/chatter_tests.js +++ b/addons/mail/static/tests/chatter_tests.js @@ -11,6 +11,7 @@ var testUtils = require('web.test_utils'); var BasicComposer = Composers.BasicComposer; +var createAsyncView = testUtils.createAsyncView; var createView = testUtils.createView; QUnit.module('mail', {}, function () { @@ -862,5 +863,86 @@ QUnit.test('followers widget: do not display follower duplications', function (a form.destroy(); }); +QUnit.module('FieldMany2ManyTagsEmail', { + beforeEach: function () { + this.data = { + partner: { + fields: { + display_name: { string: "Displayed name", type: "char" }, + timmy: { string: "pokemon", type: "many2many", relation: 'partner_type'}, + }, + records: [{ + id: 1, + display_name: "first record", + timmy: [], + }], + }, + partner_type: { + fields: { + name: {string: "Partner Type", type: "char"}, + email: {string: "Email", type: "char"}, + }, + records: [ + {id: 12, display_name: "gold", email: 'coucou@petite.perruche'}, + {id: 14, display_name: "silver", email: ''}, + ] + }, + }; + }, +}); + +QUnit.test('fieldmany2many tags email', function (assert) { + assert.expect(11); + var done = assert.async(); + + this.data.partner.records[0].timmy = [12, 14]; + + // the modals need to be closed before the form view rendering + createAsyncView({ + View: FormView, + model: 'partner', + data: this.data, + res_id: 1, + arch:'
' + + '' + + '' + + '' + + '' + + '
', + viewOptions: { + mode: 'edit', + }, + mockRPC: function (route, args) { + if (args.method ==='read' && args.model === 'partner_type') { + assert.step(args.args[0]); + assert.deepEqual(args.args[1] , ['display_name', 'email'], "should read the email"); + } + return this._super.apply(this, arguments); + }, + archs: { + 'partner_type,false,form': '
', + }, + }).then(function (form) { + // should read it 3 times (1 with the form view, one with the form dialog and one after save) + assert.verifySteps([[12, 14], [14], [14]]); + assert.strictEqual(form.$('.o_field_many2manytags[name="timmy"] span.o_tag_color_10').length, 2, + "the second tag should be present"); + + form.destroy(); + done(); + }); + + assert.strictEqual($('.modal-body.o_act_window').length, 1, + "there should be one modal opened to edit the empty email"); + assert.strictEqual($('.modal-body.o_act_window input[name="display_name"]').val(), "silver", + "the opened modal should be a form view dialog with the partner_type 14"); + assert.strictEqual($('.modal-body.o_act_window input[name="email"]').length, 1, + "there should be an email field in the modal"); + + // set the email and save the modal (will render the form view) + $('.modal-body.o_act_window input[name="email"]').val('coucou@petite.perruche').trigger('input'); + $('.modal-footer .btn-primary').click(); +}); + }); }); 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 906464a5a26..c562fa388f0 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -1187,7 +1187,8 @@ var BasicModel = AbstractModel.extend({ } var self = this; - var list = this.localData[record._changes[fieldName] || record.data[fieldName]]; + var localID = (record._changes && record._changes[fieldName]) || record.data[fieldName]; + var list = this.localData[localID]; var field = record.fields[fieldName]; var fieldInfo = record.fieldsInfo[viewType || record.viewType][fieldName]; var view = fieldInfo.views && fieldInfo.views[fieldInfo.mode]; From ce11312dba5f796b2c5769d66433aad103f30766 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Wed, 12 Jul 2017 09:36:57 +0200 Subject: [PATCH 03/33] [FIX] doc: remove nonexistant reference business removed at 411f8208 --- doc/index.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/index.rst b/doc/index.rst index dcdd6ab752b..21231b7ce3f 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -17,7 +17,6 @@ Index api_integration setup reference - business .. ifconfig:: todo_include_todos From 848e95a34cf5b1788a24c6e7a04eddc8dce91861 Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Tue, 11 Jul 2017 15:14:21 +0200 Subject: [PATCH 04/33] [FIX] point_of_sale: Printing Sales Details without logo or layout When printing a report, if the company logo or report layout is not set, it will return an action (ir.actions.act_window) to set the layout or the logo. But this change of actions does not forward the data parameters to print the price list. So it raised an error. PS: the problem comes from function "set_report_template" which doesn't take the data parameters into account when it is called from "res.company.report.form". opw:751697 --- addons/point_of_sale/i18n/point_of_sale.pot | 12 ++++++++++++ addons/point_of_sale/wizard/pos_details.py | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/addons/point_of_sale/i18n/point_of_sale.pot b/addons/point_of_sale/i18n/point_of_sale.pot index a30894348fb..5b98e317f48 100644 --- a/addons/point_of_sale/i18n/point_of_sale.pot +++ b/addons/point_of_sale/i18n/point_of_sale.pot @@ -3381,6 +3381,18 @@ msgid "You have to select a pricelist in the sale form !\n" "Please set one before choosing a product." msgstr "" +#. module: point_of_sale +#: code:addons/point_of_sale/wizard/pos_details.py:48 +#, python-format +msgid "You have to set a logo for your company." +msgstr "" + +#. module: point_of_sale +#: code:addons/point_of_sale/wizard/pos_details.py:50 +#, python-format +msgid "You have to set your reports's header and footer layout." +msgstr "" + #. module: point_of_sale #: code:addons/point_of_sale/models/pos_session.py:24 #, python-format diff --git a/addons/point_of_sale/wizard/pos_details.py b/addons/point_of_sale/wizard/pos_details.py index 0a599aefd86..e2a2500ddf5 100644 --- a/addons/point_of_sale/wizard/pos_details.py +++ b/addons/point_of_sale/wizard/pos_details.py @@ -44,6 +44,10 @@ class PosDetails(models.TransientModel): @api.multi def generate_report(self): + if (not self.env.user.company_id.logo): + raise UserError(_("You have to set a logo or a layout for your company.")) + elif (not self.env.user.company_id.external_report_layout): + raise UserError(_("You have to set your reports's header and footer layout.")) data = {'date_start': self.start_date, 'date_stop': self.end_date, 'config_ids': self.pos_config_ids.ids} return self.env['report'].get_action( [], 'point_of_sale.report_saledetails', data=data) From f5753886b200fa81c7f055dc6db4452070b8e3c9 Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Wed, 12 Jul 2017 09:45:21 +0200 Subject: [PATCH 05/33] [FIX] point_of_sale: Printing Sales Details without logo or layout Fine tuning of this commit 848e95a34cf5b1788a24c6e7a04eddc8dce91861 opw:751697 --- addons/point_of_sale/wizard/pos_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/point_of_sale/wizard/pos_details.py b/addons/point_of_sale/wizard/pos_details.py index e2a2500ddf5..fa363798dfc 100644 --- a/addons/point_of_sale/wizard/pos_details.py +++ b/addons/point_of_sale/wizard/pos_details.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import api, fields, models +from odoo import api, fields, models, _ from odoo.exceptions import UserError From ed2a77e50e94d957caca6dea309d84c55908cf93 Mon Sep 17 00:00:00 2001 From: Adrian Torres Date: Wed, 12 Jul 2017 09:45:46 +0200 Subject: [PATCH 06/33] [FIX] web: Fix FieldRadio's o_horizontal/vertical (#18163) --- .../static/src/js/fields/relational_fields.js | 2 +- .../tests/fields/relational_fields_tests.js | 34 ++++++++++++++++--- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index 567c54abcb1..822955fcf3c 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -1909,7 +1909,7 @@ var FieldRadio = FieldSelection.extend({ this._super.apply(this, arguments); if (this.mode === 'edit') { this.tagName = 'div'; - this.className += this.nodeOptions.horizontal ? 'o_horizontal' : 'o_vertical'; + this.className += this.nodeOptions.horizontal ? ' o_horizontal' : ' o_vertical'; } this.unique_id = _.uniqueId("radio"); this._setValues(); diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index d4d3a6307f9..171b3e1f787 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -30,6 +30,7 @@ QUnit.module('relational_fields', { type: "selection", selection: [['red', "Red"], ['black', "Black"]], default: 'red', + string: "Color", }, date: {string: "Some Date", type: "date"}, datetime: {string: "Datetime Field", type: 'datetime'}, @@ -1472,11 +1473,11 @@ QUnit.module('relational_fields', { }, true); assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "My little Foo Valueblipyop", - "should have the 3 rows in the correct order") + "should have the 3 rows in the correct order"); form.$buttons.find('.o_form_button_edit').click(); assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "My little Foo Valueblipyop", - "should still have the 3 rows in the correct order") + "should still have the 3 rows in the correct order"); // Drag and drop the fourth line in second position testUtils.dragAndDrop( @@ -1489,11 +1490,11 @@ QUnit.module('relational_fields', { "sequences values should be incremental starting from the previous minimum one"); assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "blipMy little Foo Valueyop", - "should have the 3 rows in the new order") + "should have the 3 rows in the new order"); form.$buttons.find('.o_form_button_save').click(); assert.strictEqual(form.$('td.o_data_cell:not(.o_handle_cell)').text(), "blipMy little Foo Valueyop", - "should still have the 3 rows in the new order") + "should still have the 3 rows in the new order"); form.destroy(); }); @@ -6412,6 +6413,31 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.test('fieldradio widget has o_horizontal or o_vertical class', function (assert) { + assert.expect(2); + + this.data.partner.fields.color2 = this.data.partner.fields.color; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '' + + '
', + }); + + var btn1 = form.$('div.o_field_radio.o_vertical'); + var btn2 = form.$('div.o_field_radio.o_horizontal'); + + assert.strictEqual(btn1.length, 1, "should have o_vertical class"); + assert.strictEqual(btn2.length, 1, "should have o_horizontal class"); + form.destroy(); + }); + QUnit.test('fieldradio widget with numerical keys encoded as strings', function (assert) { assert.expect(5); From a6f2dc07e5abded193a5f0808408314043e940f9 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Wed, 12 Jul 2017 09:28:02 +0200 Subject: [PATCH 07/33] [FIX] sale: traceback when choosing a product with warning as order.line If a user chooses a product that has a blocking error message, the product is set to False. This is good, but we do not want the following to happen: The other onchange triggers are called and "product_uom_change" generates an error because there is no product. (In fact it is "_compute_price_rule" on "product.pricelist" who raise) The onchange update the values of the other fields related to the product (eg for the "name" field where the name of the product that is canceled by the warning is assigned). --- addons/sale/models/sale.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index f73235bc361..d512037e445 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -878,6 +878,21 @@ class SaleOrderLine(models.Model): uom=self.product_uom.id ) + result = {'domain': domain} + + title = False + message = False + warning = {} + if product.sale_line_warn != 'no-message': + title = _("Warning for %s") % product.name + message = product.sale_line_warn_msg + warning['title'] = title + warning['message'] = message + result = {'warning': warning} + if product.sale_line_warn == 'block': + self.product_id = False + return result + name = product.name_get()[0][1] if product.description_sale: name += '\n' + product.description_sale @@ -889,22 +904,11 @@ class SaleOrderLine(models.Model): vals['price_unit'] = self.env['account.tax']._fix_tax_included_price(self._get_display_price(product), product.taxes_id, self.tax_id) self.update(vals) - title = False - message = False - warning = {} - if product.sale_line_warn != 'no-message': - title = _("Warning for %s") % product.name - message = product.sale_line_warn_msg - warning['title'] = title - warning['message'] = message - if product.sale_line_warn == 'block': - self.product_id = False - return {'warning': warning} - return {'domain': domain} + return result @api.onchange('product_uom', 'product_uom_qty') def product_uom_change(self): - if not self.product_uom: + if not self.product_uom or not self.product_id: self.price_unit = 0.0 return if self.order_id.pricelist_id and self.order_id.partner_id: From 4001aaf2c47d3e7e6334aaa453f58054b98ee864 Mon Sep 17 00:00:00 2001 From: Sylvain LE GAL Date: Wed, 12 Jul 2017 09:58:34 +0200 Subject: [PATCH 08/33] [FIX] l10n_fr_certification : fix string_to_hash computation - use datas of all lines, and not only the last lines. - removed useless 'move_id' field from account.move.line Was PR #18175. Courtesy of Sylvain Legal --- addons/l10n_fr_certification/models/account.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/addons/l10n_fr_certification/models/account.py b/addons/l10n_fr_certification/models/account.py index bef0880d7ca..07d5fc04e41 100644 --- a/addons/l10n_fr_certification/models/account.py +++ b/addons/l10n_fr_certification/models/account.py @@ -10,7 +10,7 @@ ERR_MSG = _("According to the french law, you cannot modify a %s in order for it #forbidden fields MOVE_FIELDS = ['date', 'journal_id', 'company_id'] -LINE_FIELDS = ['debit', 'credit', 'account_id', 'move_id', 'partner_id'] +LINE_FIELDS = ['debit', 'credit', 'account_id', 'partner_id'] class AccountMove(models.Model): @@ -55,7 +55,8 @@ class AccountMove(models.Model): for line in move.line_ids: for field in LINE_FIELDS: - values[field] = _getattrstring(line, field) + k = 'line_%d_%s' % (line.id, field) + values[k] = _getattrstring(line, field) #make the json serialization canonical # (https://tools.ietf.org/html/draft-staykov-hu-json-canonical-form-00) move.l10n_fr_string_to_hash = dumps(values, sort_keys=True, encoding="utf-8", From 566968bf99312ed4439d4274d9a7e1211d180b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mois=C3=A9s=20L=C3=B3pez?= Date: Mon, 10 Jul 2017 22:23:09 -0500 Subject: [PATCH 09/33] [FIX] doc: use new URI wkhtmltopdf releases The old release of wkhtmltopdf are no longer published on the download page. The developer explicitely asks to use the github link cf: wkhtmltopdf/wkhtmltopdf#3524 wkhtmltopdf/wkhtmltopdf#3521 wkhtmltopdf/wkhtmltopdf#3518 wkhtmltopdf/wkhtmltopdf#3508 Closes #18146 --- doc/setup/install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/setup/install.rst b/doc/setup/install.rst index de21d6c53ab..aae7d03b8ba 100644 --- a/doc/setup/install.rst +++ b/doc/setup/install.rst @@ -546,7 +546,7 @@ default db to serve on localhost:8069 http://www.enterprisedb.com/products-services-training/pgdownload .. _Quilt: http://en.wikipedia.org/wiki/Quilt_(software) .. _saas: https://www.odoo.com/page/start -.. _the wkhtmltopdf download page: http://wkhtmltopdf.org/downloads.html +.. _the wkhtmltopdf download page: https://github.com/wkhtmltopdf/wkhtmltopdf/releases/tag/0.12.1 .. _UAC: http://en.wikipedia.org/wiki/User_Account_Control .. _wkhtmltopdf: http://wkhtmltopdf.org .. _pip: https://pip.pypa.io From 058cc22d2c680f73501ce1a49762eda1a4c9ce9d Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Thu, 29 Jun 2017 14:07:01 +0200 Subject: [PATCH 10/33] [FIX] website_form: send email with subject - Use the form builder to send an email - Customize the snippet to include the subject and the body - Send a mail => The subject is not included in the email This is because the subject is filtered out and considered as a blacklisted field. It happens because `mail.mail` inherits from `mail.message`. The field property `website_form_blacklisted` is set to: - `True` for `mail.mail` - `False` for `mail.message` Since we go through all inherited models, the property on `mail.mail` is overriden by the property on `mail.message`. It is actually not necessary to go through all inherited models, since in the case of `inherits`, we copy the fields of the inherited model to the target model. The check is simplified by only checking the fields of the current model. Based on work of @nla-odoo opw-748926 --- addons/website_form/models/models.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/addons/website_form/models/models.py b/addons/website_form/models/models.py index c3e8be8d36d..65fed9e526c 100644 --- a/addons/website_form/models/models.py +++ b/addons/website_form/models/models.py @@ -18,12 +18,6 @@ class website_form_model(models.Model): website_form_default_field_id = fields.Many2one('ir.model.fields', 'Field for custom form data', domain="[('model', '=', model), ('ttype', '=', 'text')]", help="Specify the field which will contain meta and custom form fields datas.") website_form_label = fields.Char("Label for form action", help="Form action label. Ex: crm.lead could be 'Send an e-mail' and project.issue could be 'Create an Issue'.") - def _all_inherited_model_ids(self): - return list(itertools.chain( - [self.id], - *(m._all_inherited_model_ids() for m in self.inherited_model_ids) - )) - def _get_form_writable_fields(self): """ Restriction of "authorized fields" (fields which can be used in the @@ -31,16 +25,16 @@ class website_form_model(models.Model): builders and are writable. By default no field is writable by the form builder. """ - excluded = { + included = { field.name for field in self.env['ir.model.fields'].sudo().search([ - ('model_id', 'in', self._all_inherited_model_ids()), - ('website_form_blacklisted', '=', True) + ('model_id', '=', self.id), + ('website_form_blacklisted', '=', False) ]) } return { k: v for k, v in self.get_authorized_fields().iteritems() - if k not in excluded + if k in included } @api.multi From 45e7389a1e479df03f3cf8f64722b33b063f95f1 Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Wed, 12 Jul 2017 10:47:52 +0200 Subject: [PATCH 11/33] [FIX] mrp, resource: Wrong default value for Time Efficiency The time efficiency cannot be set to 0 otherwise it raised an error when creating a workorder. opw:751638, 752035 --- addons/mrp/models/mrp_workcenter.py | 2 +- addons/resource/i18n/resource.pot | 6 ++++++ addons/resource/models/resource.py | 7 +++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/addons/mrp/models/mrp_workcenter.py b/addons/mrp/models/mrp_workcenter.py index 0dc9cdc96af..cc12ec0060b 100644 --- a/addons/mrp/models/mrp_workcenter.py +++ b/addons/mrp/models/mrp_workcenter.py @@ -15,7 +15,7 @@ class MrpWorkcenter(models.Model): # resource name = fields.Char(related='resource_id.name', store=True) - time_efficiency = fields.Float('Time Efficiency', related='resource_id.time_efficiency', store=True) + time_efficiency = fields.Float('Time Efficiency', related='resource_id.time_efficiency', default=100, store=True) active = fields.Boolean('Active', related='resource_id.active', default=True, store=True) code = fields.Char('Code', copy=False) diff --git a/addons/resource/i18n/resource.pot b/addons/resource/i18n/resource.pot index 30b510ade92..aa093e5292e 100644 --- a/addons/resource/i18n/resource.pot +++ b/addons/resource/i18n/resource.pot @@ -441,6 +441,12 @@ msgstr "" msgid "This field is used to calculate the the expected duration of a work order at this work center. For example, if a work order takes one hour and the efficiency factor is 100%, then the expected duration will be one hour. If the efficiency factor is 200%, however the expected duration will be 30 minutes." msgstr "" +#. module: resource +#: code:addons/resource/models/resource.py:667 +#, python-format +msgid "The efficiency factor cannot be equal to 0." +msgstr "" + #. module: resource #: selection:resource.calendar.attendance,dayofweek:0 msgid "Thursday" diff --git a/addons/resource/models/resource.py b/addons/resource/models/resource.py index 3d09ddb55a3..9469c3847c4 100644 --- a/addons/resource/models/resource.py +++ b/addons/resource/models/resource.py @@ -659,6 +659,13 @@ class ResourceResource(models.Model): required=True, help="Define the schedule of resource") + @api.multi + @api.constrains('time_efficiency') + def _check_time_efficiency(self): + for record in self: + if record.time_efficiency == 0: + raise ValidationError(_('The efficiency factor cannot be equal to 0.')) + @api.model def create(self, values): if values.get('company_id') and not values.get('calendar_id'): From e6e17cc5d6483afbdd898248858933b85ac422d4 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Wed, 12 Jul 2017 12:38:52 +0200 Subject: [PATCH 12/33] [FIX] product: Error message when adding an item to a quote When a user tries to add a product on an opportunity, the date_order of the order is added into the context. This value can be False. When "_compute_price_rule" is called without "date" arguments (some onchange, compute...) the method method use the context date (in this case the value is False) or the today date. The date value is used in an sql request and raise a value exception. opw-751700 --- addons/product/models/product_pricelist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/product/models/product_pricelist.py b/addons/product/models/product_pricelist.py index abe09f05fd6..e6c402bc95a 100644 --- a/addons/product/models/product_pricelist.py +++ b/addons/product/models/product_pricelist.py @@ -106,7 +106,7 @@ class Pricelist(models.Model): """ self.ensure_one() if not date: - date = self._context.get('date', fields.Date.today()) + date = self._context.get('date') or fields.Date.today() if not uom_id and self._context.get('uom'): uom_id = self._context['uom'] if uom_id: From 9ee00dd265457948e0cfc8bdcca2a34064191d0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Tue, 11 Jul 2017 15:53:36 +0200 Subject: [PATCH 13/33] [FIX] mail: prevent crash when chat system is not ready When a chatter is created, appended, and destroyed before being ready, bad things could happen. This is because the code in start/other deferred was executed regardless if the widget was still alive. And, some mechanisms, such as _getBus, depends on the event system. If the widget is destroyed, it will be remove from the component tree and such functions will then return undefined. To protect agains those issues, we simply make sure that the deferred are only resolved when the widget is still alive. --- addons/mail/static/src/js/thread_field.js | 4 +- addons/mail/static/tests/chatter_tests.js | 65 +++++++++++++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/addons/mail/static/src/js/thread_field.js b/addons/mail/static/src/js/thread_field.js index 481dc49af36..0d7635cae8b 100644 --- a/addons/mail/static/src/js/thread_field.js +++ b/addons/mail/static/src/js/thread_field.js @@ -21,7 +21,7 @@ var ThreadField = AbstractField.extend(chat_mixin, { this.msgIDs = this.value.res_ids; }, willStart: function () { - return this._chatReady(); + return this.alive(this._chatReady()); }, start: function () { var self = this; @@ -43,7 +43,7 @@ var ThreadField = AbstractField.extend(chat_mixin, { var def1 = this.thread.appendTo(this.$el); var def2 = this._super.apply(this, arguments); - return $.when(def1, def2).then(function () { + return this.alive($.when(def1, def2)).then(function () { // unwrap the thread to remove an unnecessary level on div self.setElement(self.thread.$el); diff --git a/addons/mail/static/tests/chatter_tests.js b/addons/mail/static/tests/chatter_tests.js index 030539a72b9..d0b685ebbec 100644 --- a/addons/mail/static/tests/chatter_tests.js +++ b/addons/mail/static/tests/chatter_tests.js @@ -13,6 +13,7 @@ var BasicComposer = Composers.BasicComposer; var createAsyncView = testUtils.createAsyncView; var createView = testUtils.createView; +var createAsyncView = testUtils.createAsyncView; QUnit.module('mail', {}, function () { @@ -944,5 +945,69 @@ QUnit.test('fieldmany2many tags email', function (assert) { $('.modal-footer .btn-primary').click(); }); +QUnit.test('does not render and crash when destroyed before chat system is ready', function (assert) { + assert.expect(0); + + var def = $.Deferred(); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '' + + '
' + + '' + + '' + + '' + + '
' + + '
', + res_id: 2, + mockRPC: function (route, args) { + if (route === '/mail/read_followers') { + return $.when({ + followers: [], + subtypes: [], + }); + } + return this._super(route, args); + }, + intercepts: { + chat_manager_ready: function (event) { + // we delay the return of the chat_manager ready event + event.data.callback(def); + }, + get_messages: function (event) { + event.stopPropagation(); + event.data.callback($.when([{ + attachment_ids: [], + body: "", + date: moment("2016-12-20 09:35:40"), + id: 34, + res_id: 3, + author_id: ["3", "Fu Ck Mil Grom"], + }])); + }, + get_bus: function (event) { + event.stopPropagation(); + event.data.callback(new Bus()); + }, + get_session: function (event) { + event.stopPropagation(); + event.data.callback({uid: 1}); + }, + }, + }); + + form.destroy(); + // here, the chat_manager system is ready, and the chatter can try to render + // itself. We simply make sure here that no crashes occur (since the form + // view is destroyed, all rpcs will be dropped, and many other mechanisms + // relying on events will not work, such as _getBus) + def.resolve(); +}); + }); }); From 999dc80e7c5561dd866f3d0a0551a65795a9f87c Mon Sep 17 00:00:00 2001 From: Cedric Snauwaert Date: Wed, 12 Jul 2017 11:28:28 +0200 Subject: [PATCH 14/33] [FIX] base_import: when parsing date field, strip extra spaces --- addons/base_import/models/base_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/base_import/models/base_import.py b/addons/base_import/models/base_import.py index ad69cb01402..b9e22ad9925 100644 --- a/addons/base_import/models/base_import.py +++ b/addons/base_import/models/base_import.py @@ -615,7 +615,7 @@ class Import(models.TransientModel): for num, line in enumerate(data): if line[index]: try: - line[index] = dt.strftime(dt.strptime(ustr(line[index]).encode('utf-8'), user_format), server_format) + line[index] = dt.strftime(dt.strptime(ustr(line[index].strip()).encode('utf-8'), user_format), server_format) except ValueError, e: raise ValueError(_("Column %s contains incorrect values. Error in line %d: %s") % (name, num + 1, ustr(e.message))) except Exception, e: From b9a9922b62d21ce1eed545937b2b90a5961dae11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 12 Jul 2017 10:46:27 +0200 Subject: [PATCH 15/33] [FIX] web: execute action with null active_id instead of undefined Indeed pyeval that evaluates the context explicitly handles null value differently from undefined values. Let us therefore avoid crashes when it manipulates the context. Example of crash * click on forecast in project kanban view without having already created forecasts; * click on 'Assign' to create a new forecast; * selecting a task crashes because of the domain on task field that is evaluated using pyeval with an undefined active_id in context that pyeval cannot handle; --- addons/web/static/src/js/chrome/view_manager.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/js/chrome/view_manager.js b/addons/web/static/src/js/chrome/view_manager.js index 3909a7bfeaa..eeccd9f1085 100644 --- a/addons/web/static/src/js/chrome/view_manager.js +++ b/addons/web/static/src/js/chrome/view_manager.js @@ -558,7 +558,8 @@ var ViewManager = Widget.extend(ControlPanelMixin, { var self = this; var result_handler = on_closed || function () {}; var context = new Context(this.env.context, action_data.context || {}); - var record_id = res_ids && res_ids[0]; + // OR NULL hereunder: pyeval waits specifically for a null value, different from undefined + var record_id = res_ids && res_ids[0] || null; // response handler var handler = function (action) { From 68934c6b87c8b8ee7906c577dcc970baa88864c4 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Wed, 12 Jul 2017 12:26:23 +0200 Subject: [PATCH 16/33] [FIX] mass_mailing: access error when 'Retry' A user without 'Administration/Settings' access rights cannot use the 'Retry' button on a mass-mailing. This is because only this level of power is allowed to unlink `mail.mail` records. However, this button is supposed to be used by regular mass mailing users. opw-744616 --- addons/mass_mailing/models/mass_mailing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py index 7aa8ddeec56..db828aa1b75 100644 --- a/addons/mass_mailing/models/mass_mailing.py +++ b/addons/mass_mailing/models/mass_mailing.py @@ -517,7 +517,7 @@ class MassMailing(models.Model): def retry_failed_mail(self): failed_mails = self.env['mail.mail'].search([('mailing_id', 'in', self.ids), ('state', '=', 'exception')]) failed_mails.mapped('statistics_ids').unlink() - failed_mails.unlink() + failed_mails.sudo().unlink() self.write({'state': 'in_queue'}) #------------------------------------------------------ From 2d54dec94002ce68a868ac82bfe0ae38cd010e6d Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Wed, 12 Jul 2017 15:43:22 +0200 Subject: [PATCH 17/33] [FIX] website: Impossibe to open website admin with some languages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When clicking on the app website admin with the arabic language, it raised an error because this.date_from.format('YYYY-MM-DD') in function fetch_data returned an arabic date "٢٠١٧-٠٧-٠٥". opw:751960 --- addons/website/static/src/js/website.backendDashboard.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/website/static/src/js/website.backendDashboard.js b/addons/website/static/src/js/website.backendDashboard.js index 5caad853fcf..18307bba466 100644 --- a/addons/website/static/src/js/website.backendDashboard.js +++ b/addons/website/static/src/js/website.backendDashboard.js @@ -54,8 +54,8 @@ var Dashboard = Widget.extend(ControlPanelMixin, { fetch_data: function() { var self = this; return ajax.jsonRpc('/website/fetch_dashboard_data', 'call', { - 'date_from': this.date_from.format('YYYY-MM-DD'), - 'date_to': this.date_to.format('YYYY-MM-DD'), + 'date_from': this.date_from.year()+'-'+(this.date_from.month()+1)+'-'+this.date_from.date(), + 'date_to': this.date_to.year()+'-'+(this.date_to.month()+1)+'-'+this.date_to.date(), }).done(function(result) { self.data = result; self.dashboards_data = result.dashboards; From b1b83925913136f82e1af710643aeeebaca086cc Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Thu, 2 Feb 2017 09:59:29 +0100 Subject: [PATCH 18/33] [FIX] sales_team, crm: expected closing On the CRM dashboard, the shortcut 'exp. closing' is the same than the 'overdue' shortcut. However, the number of opportunities in these categories is computed differently (see method `retrieve_sales_dashboard`). We introduce a new filter "Overdue Opportunities" which reflects this difference. Backport of 9d2f48dc7fa9d5de3e850ff7ac82d9b31cf188cd opw-704173 opw-751165 --- addons/crm/crm_lead_view.xml | 3 +++ addons/sales_team/static/src/js/sales_team_dashboard.js | 2 ++ addons/sales_team/static/src/xml/sales_team_dashboard.xml | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index f5deb670046..12dddbe0e62 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -633,6 +633,9 @@ ]"/> + - + exp. closing From 3e8ba4905ccd32ea4324b8baa46e6554ce84c7ac Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Thu, 13 Jul 2017 08:32:45 +0200 Subject: [PATCH 19/33] [FIX] crm: expected closing The "exp. closing" dashboard button takes into account the won opportunities. It shouldn't. Backport of 9d2f48dc7fa9d5de3e850ff7ac82d9b31cf188cd opw-751165 opw-744940 --- addons/crm/crm_lead.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 6fedd54890b..b8902cb736d 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -1147,7 +1147,7 @@ Update your business card, phone book, social media,... Send an email right now res['closing']['today'] += 1 if date_deadline >= date.today() and date_deadline <= date.today() + timedelta(days=7): res['closing']['next_7_days'] += 1 - if date_deadline < date.today(): + if date_deadline < date.today() and not opp['date_closed']: res['closing']['overdue'] += 1 # Next activities From ec6459d42abf9b7fb0b29013b21d3235c06879eb Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Thu, 13 Jul 2017 11:12:33 +0200 Subject: [PATCH 20/33] [FIX] resource: Wrong default value for Time Efficiency The time efficiency cannot be set to 0 otherwise it raised an error when creating a workorder. opw:752251 --- addons/resource/i18n/resource.pot | 6 ++++++ addons/resource/models/resource.py | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/addons/resource/i18n/resource.pot b/addons/resource/i18n/resource.pot index 92a2ee17ecb..5256eed99ac 100644 --- a/addons/resource/i18n/resource.pot +++ b/addons/resource/i18n/resource.pot @@ -319,6 +319,12 @@ msgstr "" msgid "This field is used to calculate the the expected duration of a work order at this work center. For example, if a work order takes one hour and the efficiency factor is 100%, then the expected duration will be one hour. If the efficiency factor is 200%, however the expected duration will be 30 minutes." msgstr "" +#. module: resource +#: code:addons/resource/models/resource.py:714 +#, python-format +msgid "The efficiency factor cannot be equal to 0." +msgstr "" + #. module: resource #: selection:resource.calendar.attendance,dayofweek:0 msgid "Thursday" diff --git a/addons/resource/models/resource.py b/addons/resource/models/resource.py index b500ac8213a..b79befad18e 100644 --- a/addons/resource/models/resource.py +++ b/addons/resource/models/resource.py @@ -706,6 +706,13 @@ class ResourceResource(models.Model): help="This field is used to calculate the the expected duration of a work order at this work center. For example, if a work order takes one hour and the efficiency factor is 100%, then the expected duration will be one hour. If the efficiency factor is 200%, however the expected duration will be 30 minutes.") calendar_id = fields.Many2one("resource.calendar", string='Working Time', help="Define the schedule of resource") + @api.multi + @api.constrains('time_efficiency') + def _check_time_efficiency(self): + for record in self: + if record.time_efficiency == 0: + raise ValidationError(_('The efficiency factor cannot be equal to 0.')) + @api.multi def copy(self, default=None): self.ensure_one() From 37ff69d45e3a595824c79ecde1923b0c4a5f33f8 Mon Sep 17 00:00:00 2001 From: Goffin Simon Date: Mon, 26 Jun 2017 11:54:46 +0200 Subject: [PATCH 21/33] [FIX] mrp: Post inventory button without finished product" This fix 2332df6bc7380e742e7d3dfb945124e21078087d has been done in stable and some customers wants to keep the button "Post Inventory" available all the time. Now the button is just visible in developper mode. opw:748347 --- addons/mrp/models/mrp_production.py | 7 ++----- addons/mrp/views/mrp_production_views.xml | 2 +- addons/stock/models/product.py | 3 --- addons/stock_account/models/product.py | 3 --- 4 files changed, 3 insertions(+), 12 deletions(-) diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index 613e3f64a9f..739a62a1add 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -208,11 +208,8 @@ class MrpProduction(models.Model): @api.depends('move_raw_ids.quantity_done', 'move_finished_ids.quantity_done') def _compute_post_visible(self): for order in self: - if order.product_tmpl_id._is_cost_method_standard(): - order.post_visible = any((x.quantity_done > 0 and x.state not in ['done', 'cancel']) for x in order.move_raw_ids) or \ - any((x.quantity_done > 0 and x.state not in ['done' 'cancel']) for x in order.move_finished_ids) - else: - order.post_visible = any((x.quantity_done > 0 and x.state not in ['done' 'cancel']) for x in order.move_finished_ids) + order.post_visible = any(order.move_raw_ids.filtered(lambda x: (x.quantity_done) > 0 and (x.state not in ['done', 'cancel']))) or \ + any(order.move_finished_ids.filtered(lambda x: (x.quantity_done) > 0 and (x.state not in ['done', 'cancel']))) @api.multi @api.depends('workorder_ids.state', 'move_finished_ids') diff --git a/addons/mrp/views/mrp_production_views.xml b/addons/mrp/views/mrp_production_views.xml index 7865afdd1f2..46546c4e5e0 100644 --- a/addons/mrp/views/mrp_production_views.xml +++ b/addons/mrp/views/mrp_production_views.xml @@ -48,7 +48,7 @@ + + + + +

+ diff --git a/addons/account/static/tests/reconciliation_tests.js b/addons/account/static/tests/reconciliation_tests.js index 79841262c4f..0eeb805112c 100644 --- a/addons/account/static/tests/reconciliation_tests.js +++ b/addons/account/static/tests/reconciliation_tests.js @@ -9,6 +9,9 @@ var data = { image: {string: "image", type: 'integer'}, customer: {string: "customer", type: 'boolean'}, supplier: {string: "supplier", type: 'boolean'}, + parent_id: {string: "Parent", type: 'boolean'}, + property_account_receivable_id: {string: 'Account receivable', type: 'many2one', relation: 'account.account'}, + property_account_payable_id: {string: 'Account payable', type: 'many2one', relation: 'account.account'}, }, records: [ {id: 1, display_name: "partner 1", image: 'AAA', customer: true}, @@ -16,7 +19,7 @@ var data = { {id: 3, display_name: "partner 3", image: 'CCC', customer: true}, {id: 4, display_name: "partner 4", image: 'DDD', customer: true}, {id: 8, display_name: "Agrolait", image: 'EEE', customer: true}, - {id: 12, display_name: "Camptocamp", image: 'FFF', supplier: true}, + {id: 12, display_name: "Camptocamp", image: 'FFF', supplier: true, property_account_receivable_id: 287, property_account_payable_id: 287}, ], mark_as_reconciled: function () { return $.when(); @@ -339,7 +342,10 @@ var mv_lines = { '[]': [], '[5,"",0,6]': [ {'account_type': "receivable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-07", 'date': "2017-01-08", 'total_amount_str': "$ 650.00", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0002", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 109, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 650.00", 'debit': 650.0, 'account_code': "101200", 'ref': "", 'already_paid': false}, - {'account_type': "receivable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-07", 'date': "2017-01-08", 'total_amount_str': "$ 525.00", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0003", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 112, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 525.00", 'debit': 525.0, 'account_code': "101200", 'ref': "", 'already_paid': false} + {'account_type': "receivable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-07", 'date': "2017-01-08", 'total_amount_str': "$ 525.00", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0003", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 112, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 525.00", 'debit': 525.0, 'account_code': "101200", 'ref': "", 'already_paid': false}, + {'account_type': "receivable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-07", 'date': "2017-01-08", 'total_amount_str': "$ 650.00", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0012", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 134, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 650.00", 'debit': 650.0, 'account_id': [287, "101200 Account Receivable"], 'account_code': "101200", 'ref': "", 'already_paid': false}, + {'account_type': "receivable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-28", 'date': "2017-01-01", 'total_amount_str': "$ 4,610.00", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0001", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 106, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 4,610.00", 'debit': 4610.0, 'account_id': [287, "101200 Account Receivable"], 'account_code': "101200", 'ref': "", 'already_paid': false}, + {'account_type': "payable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-28", 'date': "2017-01-01", 'total_amount_str': "$ 10,000.00", 'partner_id': 12, 'account_name': "Account Payable", 'name': "BILL/2017/0001", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 114, 'credit': 10000.0, 'journal_id': [2, "Vendor Bills"], 'amount_str': "$ 10,000.00", 'debit': 0.0, 'account_id': [284, "101110 Stock Valuation Account"], 'account_code': "111100", 'ref': "", 'already_paid': false} ], '[5,"b",0,6]': [ {'account_type': "liquidity", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-01-23", 'date': "2017-01-23", 'total_amount_str': "$ 100.00", 'partner_id': 8, 'account_name': "Bank", 'name': "BNK1/2017/0003: CUST.IN/2017/0001", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 394, 'credit': 0.0, 'journal_id': "Bank", 'amount_str': "$ 100.00", 'debit': 100.0, 'account_code': "101401", 'ref': "", 'already_paid': true}, @@ -602,7 +608,19 @@ QUnit.module('account', { testUtils.intercept(clientAction, 'call_service', function (event) { assert.deepEqual(event.data.args[1].args, - [[5],[{partner_id: 8, counterpart_aml_dicts: [], payment_aml_ids: [109,112], new_aml_dicts: []}]], + [[5],[{partner_id: 8, counterpart_aml_dicts: [{ + "counterpart_aml_id": 109, + "credit": 650, + "debit": 0, + "name": "INV/2017/0002" + }, + { + "counterpart_aml_id": 112, + "credit": 525, + "debit": 0, + "name": "INV/2017/0003" + }], + payment_aml_ids: [], new_aml_dicts: []}]], "Should call process_reconciliations with ids"); }); @@ -625,13 +643,8 @@ QUnit.module('account', { [6], [{ partner_id: false, - counterpart_aml_dicts:[{ - name: "BNK1/2017/0002: SUPP.OUT/2017/0002", - debit: 32.58, - credit: 0, - counterpart_aml_id: 392 - }], - payment_aml_ids: [], + counterpart_aml_dicts:[], + payment_aml_ids: [392], new_aml_dicts: [] }] ], "should call process_reconciliations with partial reconcile values"); @@ -652,14 +665,14 @@ QUnit.module('account', { assert.notOk( widget.$('.cell_left .line_info_button').length, "should not display the partial reconciliation alert"); widget.$('.accounting_view thead td:first').trigger('click'); widget.$('.match .cell_account_code:first').trigger('click'); - assert.equal( widget.$('.accounting_view tbody .cell_left .line_info_button').length, 1, "should display the partial reconciliation alert"); + assert.equal( widget.$('.accounting_view tbody .cell_left .line_info_button').length, 0, "should not display the partial reconciliation alert"); assert.ok( widget.$('button.btn-primary:not(hidden)').length, "should not display the reconcile button"); assert.ok( widget.$('.text-danger:not(hidden)').length, "should display counterpart alert"); widget.$('.accounting_view .cell_left .line_info_button').trigger('click'); - assert.strictEqual(widget.$('.accounting_view .cell_left .line_info_button').length, 1, "should display a partial reconciliation alert"); - assert.ok(widget.$('.accounting_view .cell_left .line_info_button').hasClass('do_partial_reconcile_false'), "should display the partial reconciliation information"); + assert.strictEqual(widget.$('.accounting_view .cell_left .line_info_button').length, 0, "should not display a partial reconciliation alert"); + assert.notOk(widget.$('.accounting_view .cell_left .line_info_button').hasClass('do_partial_reconcile_false'), "should not display the partial reconciliation information"); assert.ok( widget.$('button.btn-default:not(hidden)').length, "should display the validate button"); - assert.strictEqual( widget.$el.data('mode'), "inactive", "should be inactive mode"); + assert.strictEqual( widget.$el.data('mode'), "match", "should be inactive mode"); widget.$('button.btn-default:not(hidden)').trigger('click'); clientAction.destroy(); @@ -722,6 +735,29 @@ QUnit.module('account', { clientAction.destroy(); }); + QUnit.test('Reconciliation change partner', function (assert) { + assert.expect(4); + + var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); + + testUtils.addMockEnvironment(clientAction, { + data: this.params.data, + }); + + clientAction.appendTo($('#qunit-fixture')); + var widget = clientAction.widgets[0]; + assert.strictEqual(widget.$('.o_input_dropdown input').val(), "Agrolait", "the partner many2one should display agrolait"); + assert.strictEqual(widget.$('.match table tr').length, 2, "agrolait should have 2 propositions for reconciliation"); + + // Simulate changing partner + widget.$('.o_input_dropdown input').trigger('click'); + $('.ui-autocomplete .ui-menu-item a:contains(Camptocamp)').trigger('mouseenter').trigger('click'); + clientAction._onAction({target: widget, name: 'change_partner', data: {data: {display_name: 'Camptocamp', id: 12}}, stopped: false}); + assert.strictEqual(widget.$('.o_input_dropdown input').val(), "Camptocamp", "the partner many2one should display Camptocamp"); + assert.strictEqual(widget.$('.match table tr').length, 3, "camptocamp should have 3 propositions for reconciliation"); + + clientAction.destroy(); + }); QUnit.test('Reconciliation create line', function (assert) { assert.expect(23); From 29c516536154b6c26e560558a44e73cc41f991b3 Mon Sep 17 00:00:00 2001 From: Richard Mathot Date: Thu, 13 Jul 2017 15:11:08 +0200 Subject: [PATCH 26/33] [FIX] website_quote: missing domain Before this commit, users could select mail.template designed for other models and lead to nasty crashes when validating a SO opw-752264 --- addons/website_quote/models/sale_quote.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/website_quote/models/sale_quote.py b/addons/website_quote/models/sale_quote.py index e65f6ed905f..79743fdc814 100644 --- a/addons/website_quote/models/sale_quote.py +++ b/addons/website_quote/models/sale_quote.py @@ -23,7 +23,8 @@ class SaleQuoteTemplate(models.Model): (2, 'Immediate after website order validation and save a token'), ], 'Payment', help="Require immediate payment by the customer when validating the order from the website quote") mail_template_id = fields.Many2one('mail.template', 'Confirmation Mail', - help="This e-mail template will be sent on confirmation. Leave empty to send nothing.") + help="This e-mail template will be sent on confirmation. Leave empty to send nothing.", + domain=[('model', '=', 'sale.quote.template')]) @api.multi def open_template(self): From 2d155a35e15843933ae344adfcac6a03d1a8d52f Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Fri, 14 Jul 2017 09:01:28 +0200 Subject: [PATCH 27/33] [FIX] website_crm_partner_assign: # of Cases The measure '# of Cases' gives a traceback when selected. opw-752165 --- addons/website_crm_partner_assign/report/crm_lead_report.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/website_crm_partner_assign/report/crm_lead_report.py b/addons/website_crm_partner_assign/report/crm_lead_report.py index 49831ba147f..c74c213cf70 100644 --- a/addons/website_crm_partner_assign/report/crm_lead_report.py +++ b/addons/website_crm_partner_assign/report/crm_lead_report.py @@ -67,7 +67,7 @@ class CrmLeadReportAssign(models.Model): p.grade_id, p.date as partner_date, c.planned_revenue*(c.probability/100) as probable_revenue, - 1 as nbr, + 1 as nbr_cases, c.create_date as create_date, extract('epoch' from (c.write_date-c.create_date))/(3600*24) as delay_close, extract('epoch' from (c.date_deadline - c.date_closed))/(3600*24) as delay_expected, From f2c7e44efe4e2bcdc9c77c66dbd56b8b4cc397b2 Mon Sep 17 00:00:00 2001 From: Khoi Nguyen Date: Thu, 13 Jul 2017 17:40:35 +0200 Subject: [PATCH 28/33] [FIX] web: no M2ODialog when pressing enter in a many2one Before this revision, pressing ENTER when inside a many2one field triggers a 'focus out' event. In particular, if one creates a many2one record and uses the ENTER key to click "Create and Edit", two dialog windows appear: one to actually create the associated record, and a second to alert the user that they are creating an associated record. The latter appears every time the many2one field loses focus. This commit changes the 'key up' event for many2one fields to ensure that the ENTER key does not trigger a 'focus out' event. --- .../static/src/js/fields/relational_fields.js | 10 +++- .../tests/fields/relational_fields_tests.js | 46 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index 822955fcf3c..14512c02912 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -485,8 +485,16 @@ var FieldMany2One = AbstractField.extend({ }, /** * @private + * + * @param {OdooEvent} ev */ - _onInputKeyup: function () { + _onInputKeyup: function (ev) { + if (ev.which === $.ui.keyCode.ENTER) { + // If we pressed enter, we want to prevent _onInputFocusout from + // executing since it would open a M2O dialog to request + // confirmation that the many2one is not properly set. + return; + } if (this.$input.val() === "") { this.reinitialize(false); } else if (this._getDisplayName(this.m2o_value) !== this.$input.val()) { diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index 171b3e1f787..aacbb744252 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -1141,6 +1141,52 @@ QUnit.module('relational_fields', { }); }); + QUnit.test('pressing ENTER on a \'no_quick_create\' many2one should not trigger M2ODialog', function (assert) { + var done = assert.async(); + assert.expect(1); + + var M2O_DELAY = relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY; + relationalFields.FieldMany2One.prototype.AUTOCOMPLETE_DELAY = 0; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
' + + '' + + '' + + '', + archs: { + 'partner,false,form': '
', + }, + }); + + var $input = form.$('.o_field_many2one input'); + $input.val("Something that does not exist").trigger('input'); + $('.ui-autocomplete .ui-menu-item a:contains(Create and)').trigger('mouseenter'); + concurrency.delay(0).then(function() { + $input.trigger($.Event('keydown', { + which: $.ui.keyCode.ENTER, + keyCode: $.ui.keyCode.ENTER, + })); + $input.trigger($.Event('keypress', { + which: $.ui.keyCode.ENTER, + keyCode: $.ui.keyCode.ENTER, + })); + $input.trigger($.Event('keyup', { + which: $.ui.keyCode.ENTER, + keyCode: $.ui.keyCode.ENTER, + })); + concurrency.delay(0).then(function() { + $input.blur(); + assert.strictEqual($('.modal').length, 1, + "should have one modal in body"); + form.destroy(); + done(); + }); + }); + }); + QUnit.test('many2one in editable list + onchange, with enter [REQUIRE FOCUS]', function (assert) { assert.expect(6); var done = assert.async(); From 1d3d665f2c60f9cdce78b1c6198a83c954085a2e Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Fri, 14 Jul 2017 10:37:09 +0200 Subject: [PATCH 29/33] [FIX] doc: adapt to removal of workflows Remove one excerice that was in the workflow section Adapt the diff --- doc/howtos/backend/exercise-dashboard | 1 + doc/howtos/backend/exercise-state-basic | 55 ----------------------- doc/howtos/backend/exercise-translations | 4 +- doc/howtos/backend/exercise-wizard-action | 2 +- doc/howtos/backend/exercise-wizard-launch | 2 +- doc/howtos/backend/exercise-wizard-multi | 2 +- doc/howtos/backend/series | 1 - 7 files changed, 6 insertions(+), 61 deletions(-) delete mode 100644 doc/howtos/backend/exercise-state-basic diff --git a/doc/howtos/backend/exercise-dashboard b/doc/howtos/backend/exercise-dashboard index 91bc239dd7a..6d0aa53fd83 100644 --- a/doc/howtos/backend/exercise-dashboard +++ b/doc/howtos/backend/exercise-dashboard @@ -15,6 +15,7 @@ Index: addons/openacademy/__manifest__.py # always loaded 'data': [ @@ -31,6 +31,7 @@ + 'templates.xml', 'views/openacademy.xml', 'views/partner.xml', + 'views/session_board.xml', diff --git a/doc/howtos/backend/exercise-state-basic b/doc/howtos/backend/exercise-state-basic deleted file mode 100644 index f70a41b4dae..00000000000 --- a/doc/howtos/backend/exercise-state-basic +++ /dev/null @@ -1,55 +0,0 @@ -# HG changeset patch -# Parent 8c721171aa16a41e94059f53d6780c67b5ef2dfc -# Parent 8d2ca42b5be2031ea9624896df53f09f7ca131be - -diff --git a/openacademy/models.py b/openacademy/models.py ---- a/openacademy/models.py -+++ b/openacademy/models.py -@@ -66,6 +66,24 @@ class Session(models.Model): - attendees_count = fields.Integer( - string="Attendees count", compute='_get_attendees_count', store=True) - -+ state = fields.Selection([ -+ ('draft', "Draft"), -+ ('confirmed', "Confirmed"), -+ ('done', "Done"), -+ ], default='draft') -+ -+ @api.multi -+ def action_draft(self): -+ self.state = 'draft' -+ -+ @api.multi -+ def action_confirm(self): -+ self.state = 'confirmed' -+ -+ @api.multi -+ def action_done(self): -+ self.state = 'done' -+ - @api.depends('seats', 'attendee_ids') - def _taken_seats(self): - for r in self: -diff --git a/openacademy/views/openacademy.xml b/openacademy/views/openacademy.xml ---- a/openacademy/views/openacademy.xml -+++ b/openacademy/views/openacademy.xml -@@ -96,6 +96,19 @@ - openacademy.session - -
-+
-+
-+ - - - diff --git a/doc/howtos/backend/exercise-translations b/doc/howtos/backend/exercise-translations index 8ade655253d..e7dfe53ebb7 100644 --- a/doc/howtos/backend/exercise-translations +++ b/doc/howtos/backend/exercise-translations @@ -28,7 +28,7 @@ diff --git a/openacademy/models.py b/openacademy/models.py default['name'] = new_name return super(Course, self).copy(default) -@@ -97,15 +97,15 @@ class Session(models.Model): +@@ -79,15 +79,15 @@ class Session(models.Model): if self.seats < 0: return { 'warning': { @@ -48,7 +48,7 @@ diff --git a/openacademy/models.py b/openacademy/models.py }, } -@@ -151,4 +151,4 @@ class Session(models.Model): +@@ -133,4 +133,4 @@ class Session(models.Model): def _check_instructor_not_in_attendees(self): for r in self: if r.instructor_id and r.instructor_id in r.attendee_ids: diff --git a/doc/howtos/backend/exercise-wizard-action b/doc/howtos/backend/exercise-wizard-action index 3bc20624ee9..23d6a0f2667 100644 --- a/doc/howtos/backend/exercise-wizard-action +++ b/doc/howtos/backend/exercise-wizard-action @@ -2,7 +2,7 @@ Index: addons/openacademy/views/openacademy.xml =================================================================== --- addons.orig/openacademy/views/openacademy.xml 2014-08-28 14:34:19.255004611 +0200 +++ addons/openacademy/views/openacademy.xml 2014-08-28 14:34:19.251004612 +0200 -@@ -248,6 +248,12 @@ +@@ -234,6 +234,12 @@ diff --git a/doc/howtos/backend/exercise-wizard-launch b/doc/howtos/backend/exercise-wizard-launch index d864eef3f87..340545ededa 100644 --- a/doc/howtos/backend/exercise-wizard-launch +++ b/doc/howtos/backend/exercise-wizard-launch @@ -17,7 +17,7 @@ Index: addons/openacademy/views/openacademy.xml =================================================================== --- addons.orig/openacademy/views/openacademy.xml 2014-08-28 14:34:07.879004780 +0200 +++ addons/openacademy/views/openacademy.xml 2014-08-28 14:34:07.871004780 +0200 -@@ -237,5 +237,26 @@ +@@ -224,5 +224,26 @@ diff --git a/doc/howtos/backend/exercise-wizard-multi b/doc/howtos/backend/exercise-wizard-multi index dc99dba2e31..f8ece675090 100644 --- a/doc/howtos/backend/exercise-wizard-multi +++ b/doc/howtos/backend/exercise-wizard-multi @@ -2,7 +2,7 @@ Index: addons/openacademy/views/openacademy.xml =================================================================== --- addons.orig/openacademy/views/openacademy.xml 2014-08-28 14:34:28.583004473 +0200 +++ addons/openacademy/views/openacademy.xml 2014-08-28 14:34:28.579004473 +0200 -@@ -245,7 +245,7 @@ +@@ -231,7 +231,7 @@ diff --git a/doc/howtos/backend/series b/doc/howtos/backend/series index 36627e4eadd..e48d05914e9 100644 --- a/doc/howtos/backend/series +++ b/doc/howtos/backend/series @@ -24,7 +24,6 @@ exercise-searchview exercise-gantt exercise-graph exercise-kanban -exercise-state-basic exercise-access-rights exercise-access-rules exercise-wizard From 0323b7154b629942ed0fc34711b7f4bb6ded8282 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Fri, 14 Jul 2017 10:47:35 +0200 Subject: [PATCH 30/33] [FIX] doc: correct diff The attendee_ids line was highlighted instead of the session_ids --- doc/howtos/backend/exercise-wizard-multi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/howtos/backend/exercise-wizard-multi b/doc/howtos/backend/exercise-wizard-multi index dc99dba2e31..4a9e2ae7415 100644 --- a/doc/howtos/backend/exercise-wizard-multi +++ b/doc/howtos/backend/exercise-wizard-multi @@ -2,7 +2,7 @@ Index: addons/openacademy/views/openacademy.xml =================================================================== --- addons.orig/openacademy/views/openacademy.xml 2014-08-28 14:34:28.583004473 +0200 +++ addons/openacademy/views/openacademy.xml 2014-08-28 14:34:28.579004473 +0200 -@@ -245,7 +245,7 @@ +@@ -244,7 +244,7 @@ From bb7fef79a619460500e630ae6013724b29a65829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mois=C3=A9s=20L=C3=B3pez?= Date: Thu, 13 Jul 2017 22:41:48 -0500 Subject: [PATCH 31/33] [IMP] doc: Add 'report' dependency To be able to correctly use report features. It is auto-install but a good idea to make sure it is present and to show the report module Closes #18220 --- doc/howtos/backend/exercise-dashboard | 4 ++-- doc/howtos/backend/exercise-report | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/doc/howtos/backend/exercise-dashboard b/doc/howtos/backend/exercise-dashboard index 4a6eceb8fd9..85cccc2c4cf 100644 --- a/doc/howtos/backend/exercise-dashboard +++ b/doc/howtos/backend/exercise-dashboard @@ -9,8 +9,8 @@ Index: addons/openacademy/__manifest__.py 'version': '0.1', # any module necessary for this one to work correctly -- 'depends': ['base'], -+ 'depends': ['base', 'board'], +- 'depends': ['base', 'report'], ++ 'depends': ['base', 'report', 'board'], # always loaded 'data': [ diff --git a/doc/howtos/backend/exercise-report b/doc/howtos/backend/exercise-report index 6e8a2d78705..d3cbb2c0e49 100644 --- a/doc/howtos/backend/exercise-report +++ b/doc/howtos/backend/exercise-report @@ -1,6 +1,19 @@ # HG changeset patch # Parent c140f0a861a08881d8737bca0ffb83904a2059a3 +Index: addons/openacademy/__manifest__.py +=================================================================== +--- addons.orig/openacademy/__manifest__.py 2014-08-26 17:26:21.535783052 +0200 ++++ addons/openacademy/__manifest__.py 2014-08-26 17:26:21.531783052 +0200 +@@ -21,7 +21,7 @@ + 'version': '0.1', + + # any module necessary for this one to work correctly +- 'depends': ['base'], ++ 'depends': ['base', 'report'], + + # always loaded + 'data': [ Index: addons/openacademy/__manifest__.py =================================================================== --- addons.orig/openacademy/__manifest__.py 2014-08-29 08:39:43.975536806 +0200 From ba5608a5af9798508ed5646e01eb13fdcda863e4 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Fri, 14 Jul 2017 10:39:56 +0200 Subject: [PATCH 32/33] [FIX] resource: efficiency factor Backport of 45e7389a1e479df03f3cf8f64722b33b063f95f1 opw-752226 --- addons/resource/i18n/resource.pot | 6 ++++++ addons/resource/models/resource.py | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/addons/resource/i18n/resource.pot b/addons/resource/i18n/resource.pot index e80f7c97d44..3a9285ebffc 100644 --- a/addons/resource/i18n/resource.pot +++ b/addons/resource/i18n/resource.pot @@ -319,6 +319,12 @@ msgstr "" msgid "Sunday" msgstr "" +#. module: resource +#: code:addons/resource/models/resource.py:714 +#, python-format +msgid "The efficiency factor cannot be equal to 0." +msgstr "" + #. module: resource #: model:ir.model.fields,help:resource.field_resource_resource_time_efficiency msgid "This field depict the efficiency of the resource to complete tasks. e.g resource put alone on a phase of 5 days with 5 tasks assigned to him, will show a load of 100% for this phase by default, but if we put a efficiency of 200%, then his load will only be 50%." diff --git a/addons/resource/models/resource.py b/addons/resource/models/resource.py index 36f22ea1ffa..5d9ef4c3abe 100644 --- a/addons/resource/models/resource.py +++ b/addons/resource/models/resource.py @@ -706,6 +706,13 @@ class ResourceResource(models.Model): help="This field depict the efficiency of the resource to complete tasks. e.g resource put alone on a phase of 5 days with 5 tasks assigned to him, will show a load of 100% for this phase by default, but if we put a efficiency of 200%, then his load will only be 50%.") calendar_id = fields.Many2one("resource.calendar", string='Working Time', help="Define the schedule of resource") + @api.multi + @api.constrains('time_efficiency') + def _check_time_efficiency(self): + for record in self: + if record.time_efficiency == 0: + raise ValidationError(_('The efficiency factor cannot be equal to 0.')) + @api.multi def copy(self, default=None): self.ensure_one() From 1dee64eda8fdbfa066c882f0f82a44532079167b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mois=C3=A9s=20L=C3=B3pez?= Date: Fri, 14 Jul 2017 03:56:18 -0500 Subject: [PATCH 33/33] [FIX] doc: correct diff lines to highlight the modified line Closes #18230 --- doc/howtos/backend/exercise-dashboard | 2 +- doc/howtos/backend/exercise-report | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/howtos/backend/exercise-dashboard b/doc/howtos/backend/exercise-dashboard index 6d0aa53fd83..ba5c1320623 100644 --- a/doc/howtos/backend/exercise-dashboard +++ b/doc/howtos/backend/exercise-dashboard @@ -14,7 +14,7 @@ Index: addons/openacademy/__manifest__.py # always loaded 'data': [ -@@ -31,6 +31,7 @@ +@@ -30,6 +30,7 @@ 'templates.xml', 'views/openacademy.xml', 'views/partner.xml', diff --git a/doc/howtos/backend/exercise-report b/doc/howtos/backend/exercise-report index 351d34a92b2..797174e247a 100644 --- a/doc/howtos/backend/exercise-report +++ b/doc/howtos/backend/exercise-report @@ -5,7 +5,7 @@ Index: addons/openacademy/__manifest__.py =================================================================== --- addons.orig/openacademy/__manifest__.py 2014-08-29 08:39:43.975536806 +0200 +++ addons/openacademy/__manifest__.py 2014-08-29 08:39:52.000000000 +0200 -@@ -31,6 +31,7 @@ +@@ -30,6 +30,7 @@ 'templates.xml', 'views/openacademy.xml', 'views/partner.xml',