diff --git a/addons/account/i18n/account.pot b/addons/account/i18n/account.pot index 3d8be6134ce..6a2d2722232 100644 --- a/addons/account/i18n/account.pot +++ b/addons/account/i18n/account.pot @@ -9688,6 +9688,12 @@ msgstr "" msgid "You have" msgstr "" +#. module: account +#: code:addons/account/models/account_payment.py:360 +#, python-format +msgid "You have to define a sequence for %s in your company." +msgstr "" + #. module: account #: code:addons/account/wizard/account_report_general_ledger.py:21 #, python-format diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 1bcf86010aa..e51bffa4d33 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -116,7 +116,7 @@ class AccountInvoice(models.Model): def _get_outstanding_info_JSON(self): self.outstanding_credits_debits_widget = json.dumps(False) if self.state == 'open': - domain = [('account_id', '=', self.account_id.id), ('partner_id', '=', self.env['res.partner']._find_accounting_partner(self.partner_id).id), ('reconciled', '=', False), ('amount_residual', '!=', 0.0)] + domain = [('account_id', '=', self.account_id.id), ('partner_id', '=', self.env['res.partner']._find_accounting_partner(self.partner_id).id), ('reconciled', '=', False), '|', ('amount_residual', '!=', 0.0), ('amount_residual_currency', '!=', 0.0)] if self.type in ('out_invoice', 'in_refund'): domain.extend([('credit', '>', 0), ('debit', '=', 0)]) type_payment = _('Outstanding credits') diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py index d4491f7cd77..217e184c4af 100644 --- a/addons/account/models/account_payment.py +++ b/addons/account/models/account_payment.py @@ -455,6 +455,8 @@ class account_payment(models.Model): if rec.payment_type == 'outbound': sequence_code = 'account.payment.supplier.invoice' rec.name = self.env['ir.sequence'].with_context(ir_sequence_date=rec.payment_date).next_by_code(sequence_code) + if not rec.name and self.payment_type != 'transfer': + raise UserError(_("You have to define a sequence for %s in your company.") % (sequence_code,)) # Create the journal entry amount = rec.amount * (rec.payment_type in ('outbound', 'transfer') and 1 or -1) diff --git a/addons/account/report/account_invoice_report.py b/addons/account/report/account_invoice_report.py index 1bcfab43b06..4753ce92a86 100644 --- a/addons/account/report/account_invoice_report.py +++ b/addons/account/report/account_invoice_report.py @@ -110,7 +110,7 @@ class AccountInvoiceReport(models.Model): ai.type, ai.state, pt.categ_id, ai.date_due, ai.account_id, ail.account_id AS account_line_id, ai.partner_bank_id, SUM ((invoice_type.sign * ail.quantity) / u.factor * u2.factor) AS product_qty, - SUM(ail.price_subtotal_signed) AS price_total, + SUM(ail.price_subtotal_signed * invoice_type.sign) AS price_total, SUM(ABS(ail.price_subtotal_signed)) / CASE WHEN SUM(ail.quantity / u.factor * u2.factor) <> 0::numeric THEN SUM(ail.quantity / u.factor * u2.factor) diff --git a/addons/l10n_mx/models/chart_template.py b/addons/l10n_mx/models/chart_template.py index 4e1397073ba..d6607a7a359 100644 --- a/addons/l10n_mx/models/chart_template.py +++ b/addons/l10n_mx/models/chart_template.py @@ -16,6 +16,7 @@ class AccountChartTemplate(models.Model): if not self == self.env.ref('l10n_mx.mx_coa'): return res journal_basis = self.env['account.journal'].search([ + ('company_id', '=', company.id), ('type', '=', 'general'), ('code', '=', 'CBMX')], limit=1) company.write({'tax_cash_basis_journal_id': journal_basis.id}) diff --git a/addons/mass_mailing/models/mass_mailing_stats.py b/addons/mass_mailing/models/mass_mailing_stats.py index e330975fe5f..cea7ee28a63 100644 --- a/addons/mass_mailing/models/mass_mailing_stats.py +++ b/addons/mass_mailing/models/mass_mailing_stats.py @@ -20,7 +20,8 @@ class MailMailStats(models.Model): string='Mail ID (tech)', help='ID of the related mail_mail. This field is an integer field because ' 'the related mail_mail can be deleted separately from its statistics. ' - 'However the ID is needed for several action and controllers.' + 'However the ID is needed for several action and controllers.', + index=True, ) message_id = fields.Char(string='Message-ID') model = fields.Char(string='Document model') diff --git a/addons/purchase/models/stock.py b/addons/purchase/models/stock.py index 7abc5bd898b..bb442538229 100644 --- a/addons/purchase/models/stock.py +++ b/addons/purchase/models/stock.py @@ -16,10 +16,10 @@ class StockPicking(models.Model): res = super(StockPicking, self)._create_backorder(backorder_moves) for picking in self: if picking.picking_type_id.code == 'incoming': - backorder = self.search([('backorder_id', '=', picking.id)]) - backorder.message_post_with_view('mail.message_origin_link', - values={'self': backorder, 'origin': backorder.purchase_id}, - subtype_id=self.env.ref('mail.mt_note').id) + for backorder in self.search([('backorder_id', '=', picking.id)]): + backorder.message_post_with_view('mail.message_origin_link', + values={'self': backorder, 'origin': backorder.purchase_id}, + subtype_id=self.env.ref('mail.mt_note').id) return res diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index c2bd4f5d5b8..4cbbf498e82 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -93,8 +93,12 @@ var FieldMany2One = AbstractField.extend({ this._super.apply(this, arguments); this.limit = 7; this.orderer = new concurrency.DropMisordered(); - this.can_create = ('can_create' in this.attrs ? this.attrs.can_create : true) && !this.nodeOptions.no_create; - this.can_write = 'can_write' in this.attrs ? this.attrs.can_write : true; + + // should normally also be set, except in standalone M20 + this.can_create = ('can_create' in this.attrs ? JSON.parse(this.attrs.can_create) : true) && + !this.nodeOptions.no_create; + this.can_write = 'can_write' in this.attrs ? JSON.parse(this.attrs.can_write) : true; + this.nodeOptions = _.defaults(this.nodeOptions, { quick_create: true, }); diff --git a/addons/web/static/src/js/fields/upgrade_fields.js b/addons/web/static/src/js/fields/upgrade_fields.js index ae8cae56ef3..c2c30760abf 100644 --- a/addons/web/static/src/js/fields/upgrade_fields.js +++ b/addons/web/static/src/js/fields/upgrade_fields.js @@ -52,9 +52,9 @@ var AbstractFieldUpgrade = { * * @abstract * @private - * @param {JQuery} the 'Enterprise' label to insert + * @param {jQuery} $enterpriseLabel the 'Enterprise' label to insert */ - _insertEnterpriseLabel: function ($enterprise_label) {}, + _insertEnterpriseLabel: function ($enterpriseLabel) {}, /** * Opens the Upgrade dialog. * @@ -127,6 +127,15 @@ var UpgradeBoolean = FieldBoolean.extend(AbstractFieldUpgrade, { events: _.extend({}, AbstractField.prototype.events, { 'click input': '_onInputClicked', }), + /** + * Re-renders the widget with the label + * + * @param {jQuery} $label + */ + renderWithLabel: function ($label) { + this.$label = $label; + this._render(); + }, //-------------------------------------------------------------------------- // Private @@ -136,8 +145,9 @@ var UpgradeBoolean = FieldBoolean.extend(AbstractFieldUpgrade, { * @override * @private */ - _insertEnterpriseLabel: function ($enterprise_label) { - this.$el.append(' ').append($enterprise_label); + _insertEnterpriseLabel: function ($enterpriseLabel) { + var $el = this.$label || this.$el; + $el.append(' ').append($enterpriseLabel); }, /** * @override @@ -170,8 +180,8 @@ var UpgradeRadio = FieldRadio.extend(AbstractFieldUpgrade, { * @override * @private */ - _insertEnterpriseLabel: function ($enterprise_label) { - this.$('label').last().append(' ').append($enterprise_label); + _insertEnterpriseLabel: function ($enterpriseLabel) { + this.$('label').last().append(' ').append($enterpriseLabel); }, /** * @override diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js index db026536060..6b9949897ca 100644 --- a/addons/web/static/src/js/views/form/form_renderer.js +++ b/addons/web/static/src/js/views/form/form_renderer.js @@ -808,11 +808,16 @@ var FormRenderer = BasicRenderer.extend({ // Attach the tooltips on the fields' label _.each(this.allFieldWidgets[this.state.id], function (widget) { + var idForLabel = self.idsForLabels[widget.name]; + var $label = idForLabel ? self.$('label[for=' + idForLabel + ']') : $(); if (core.debug || widget.attrs.help || widget.field.help) { - var idForLabel = self.idsForLabels[widget.name]; - var $label = idForLabel ? self.$('label[for=' + idForLabel + ']') : $(); self._addFieldTooltip(widget, $label); } + if (widget.attrs.widget === 'upgrade_boolean') { + // this widget needs a reference to its $label to be correctly + // rendered + widget.renderWithLabel($label); + } }); }, /** diff --git a/addons/web/static/src/js/views/kanban/kanban_model.js b/addons/web/static/src/js/views/kanban/kanban_model.js index 354657eb0d9..f37db7eff94 100644 --- a/addons/web/static/src/js/views/kanban/kanban_model.js +++ b/addons/web/static/src/js/views/kanban/kanban_model.js @@ -141,6 +141,9 @@ var KanbanModel = BasicModel.extend({ id: new_group.res_id, display_name: new_group.value, }; + } else if (groupedField.type === 'selection') { + var value = _.findWhere(groupedField.selection, {1: new_group.value}); + changes[groupedFieldName] = value && value[0] || false; } else { changes[groupedFieldName] = new_group.value; } diff --git a/addons/web/static/src/js/views/kanban/kanban_renderer.js b/addons/web/static/src/js/views/kanban/kanban_renderer.js index 0bbc7aa2698..cd9572b0fc5 100644 --- a/addons/web/static/src/js/views/kanban/kanban_renderer.js +++ b/addons/web/static/src/js/views/kanban/kanban_renderer.js @@ -138,10 +138,10 @@ var KanbanRenderer = BasicRenderer.extend({ * @returns {Deferred} */ updateColumn: function (localID, columnState) { - var column = _.findWhere(this.widgets, {db_id: localID}); - this.widgets.splice(_.indexOf(this.widgets, column), 1); // remove column from widgets' list var newColumn = new KanbanColumn(this, columnState, this.columnOptions, this.recordOptions); - this.widgets.push(newColumn); + var index = _.findIndex(this.widgets, {db_id: localID}); + var column = this.widgets[index]; + this.widgets[index] = newColumn; return newColumn.insertAfter(column.$el).then(column.destroy.bind(column)); }, /** diff --git a/addons/web/static/src/js/views/list/list_editable_renderer.js b/addons/web/static/src/js/views/list/list_editable_renderer.js index 229c61733e5..7984ebf21e1 100644 --- a/addons/web/static/src/js/views/list/list_editable_renderer.js +++ b/addons/web/static/src/js/views/list/list_editable_renderer.js @@ -477,6 +477,9 @@ ListRenderer.include({ return this._selectRow(rowIndex).then(function () { var record = self.state.data[rowIndex]; var correctedIndex = colIndex - getNbButtonBefore(colIndex); + if (correctedIndex >= (self.allFieldWidgets[record.id] || []).length) { + return $.Deferred().reject(); + } var fieldIndex = self._activateFieldWidget(record, correctedIndex, { inc: 1, wrap: wrap, diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js index 2020e0124f3..75ee8e8bf3e 100644 --- a/addons/web/static/tests/fields/relational_fields_tests.js +++ b/addons/web/static/tests/fields/relational_fields_tests.js @@ -1362,6 +1362,56 @@ QUnit.module('relational_fields', { form.destroy(); }); + QUnit.test('can_create and can_write option on a many2one', function (assert) { + assert.expect(5); + + this.data.product.options = { + can_create: "false", + can_write: "false", + }; + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '
', + archs: { + 'product,false,form': '', + }, + mockRPC: function (route) { + if (route === '/web/dataset/call_kw/product/get_formview_id') { + return $.when(false); + } + return this._super.apply(this, arguments); + }, + }); + + form.$('.o_field_many2one input').click(); + assert.strictEqual($('.ui-autocomplete .o_m2o_dropdown_option:contains(Create)').length, 0, + "there shouldn't be any option to search and create"); + + $('.ui-autocomplete li:contains(xpad)').mouseenter().click(); + assert.strictEqual(form.$('.o_field_many2one input').val(), "xpad", + "the correct record should be selected"); + assert.strictEqual(form.$('.o_field_many2one .o_external_button').length, 1, + "there should be an external button displayed"); + + form.$('.o_field_many2one .o_external_button').click(); + assert.strictEqual($('.modal .o_form_view.o_form_readonly').length, 1, + "there should be a readonly form view opened"); + + $('.modal .o_form_button_cancel').click(); + + form.$('.o_field_many2one input').val('new product').trigger('keyup').trigger('focusout'); + + assert.strictEqual($('.modal').length, 0, "should not display the create modal"); + form.destroy(); + }); + QUnit.test('pressing enter in a m2o in an editable list', function (assert) { assert.expect(9); var done = assert.async(); diff --git a/addons/web/static/tests/fields/upgrade_fields_tests.js b/addons/web/static/tests/fields/upgrade_fields_tests.js index babc32b60a8..427ec7b54e3 100644 --- a/addons/web/static/tests/fields/upgrade_fields_tests.js +++ b/addons/web/static/tests/fields/upgrade_fields_tests.js @@ -39,6 +39,28 @@ QUnit.module('upgrade_fields', { form.destroy(); }); + QUnit.test('widget upgrade_boolean in a form view', function (assert) { + assert.expect(3); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: '', + }); + + assert.strictEqual(form.$('.o_field .label').length, 0, + "the upgrade label shouldn't be inside the field section"); + assert.strictEqual(form.$('.o_label .label').length, 1, + "the upgrade label should be inside the label section"); + assert.strictEqual(form.$('.o_label').text(), "Bar EnterpriseCoucou", + "the upgrade label should be inside the label section"); + form.destroy(); + }); + }); }); }); diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js index eb78153265c..9523f8b0c0f 100644 --- a/addons/web/static/tests/helpers/mock_server.js +++ b/addons/web/static/tests/helpers/mock_server.js @@ -255,6 +255,10 @@ var MockServer = Class.extend({ var relModel, relFields; _.each(fieldNodes, function (node, name) { var field = fields[name]; + if (field.type === "many2one" || field.type === "many2many") { + node.attrs.can_create = node.attrs.can_create || "true"; + node.attrs.can_write = node.attrs.can_write || "true"; + } if (field.type === "one2many" || field.type === "many2many") { field.views = {}; _.each(node.children, function (children) { diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js index b7ebf526dcb..b769456b2ea 100644 --- a/addons/web/static/tests/views/kanban_tests.js +++ b/addons/web/static/tests/views/kanban_tests.js @@ -551,6 +551,48 @@ QUnit.module('Views', { kanban.destroy(); }); + QUnit.test('drag and drop a record, grouped by selection', function (assert) { + assert.expect(6); + + var kanban = createView({ + View: KanbanView, + model: 'partner', + data: this.data, + arch: '