diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 249480c0ee4..afd0fe1019b 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -227,7 +227,7 @@ class AccountInvoice(models.Model): for line in self.move_id.line_ids.filtered(lambda l: l.account_id.id == self.account_id.id): payment_lines.update(line.mapped('matched_credit_ids.credit_move_id.id')) payment_lines.update(line.mapped('matched_debit_ids.debit_move_id.id')) - self.payment_move_line_ids = self.env['account.move.line'].browse(list(payment_lines)) + self.payment_move_line_ids = self.env['account.move.line'].browse(list(payment_lines)).sorted() name = fields.Char(string='Reference/Description', index=True, readonly=True, states={'draft': [('readonly', False)]}, copy=False, help='The name that will be used on account move lines') diff --git a/addons/mail/models/mail_mail.py b/addons/mail/models/mail_mail.py index ad51de52369..4671c71b1ba 100644 --- a/addons/mail/models/mail_mail.py +++ b/addons/mail/models/mail_mail.py @@ -115,6 +115,8 @@ class MailMail(models.Model): ids = filtered_ids else: ids = list(set(filtered_ids) & set(ids)) + ids.sort() + res = None try: # auto-commit except in testing mode diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml index 7b524b9513a..499ca3918ee 100644 --- a/addons/project/views/project_views.xml +++ b/addons/project/views/project_views.xml @@ -65,7 +65,7 @@ project.task kanban,tree,form,calendar,pivot,graph { - 'group_by': 'stage_id', + 'default_group_by': 'stage_id', 'search_default_project_id': [active_id], 'default_project_id': active_id, } diff --git a/addons/stock/models/stock_move_line.py b/addons/stock/models/stock_move_line.py index 38f064685fb..9f2529a6df6 100644 --- a/addons/stock/models/stock_move_line.py +++ b/addons/stock/models/stock_move_line.py @@ -510,6 +510,8 @@ class StockMoveLine(models.Model): candidate.product_uom_qty = 0.0 else: candidate.unlink() + if float_is_zero(quantity, precision_rounding=rounding): + break else: # split this move line and assign the new part to our extra move quantity_split = float_round( @@ -517,8 +519,6 @@ class StockMoveLine(models.Model): precision_rounding=self.product_uom_id.rounding, rounding_method='UP') candidate.product_uom_qty = self.product_id.uom_id._compute_quantity(quantity_split, candidate.product_uom_id, rounding_method='HALF-UP') - quantity -= quantity_split move_to_recompute_state |= candidate.move_id - if quantity == 0.0: break move_to_recompute_state._recompute_state() diff --git a/addons/stock/tests/test_move.py b/addons/stock/tests/test_move.py index 6df567f4b66..50ae3424060 100644 --- a/addons/stock/tests/test_move.py +++ b/addons/stock/tests/test_move.py @@ -2004,6 +2004,48 @@ class StockMove(TransactionCase): self.assertAlmostEqual(move_pack_cust.reserved_availability, 1.0) self.assertEqual(move_pack_cust.state, 'partially_available') + def test_use_reserved_move_line_1(self): + """ Test that _free_reservation work when quantity is only available on + reserved move lines. + """ + self.env['stock.quant']._update_available_quantity(self.product1, self.stock_location, 10.0) + move1 = self.env['stock.move'].create({ + 'name': 'test_use_unreserved_move_line_1_1', + 'location_id': self.stock_location.id, + 'location_dest_id': self.customer_location.id, + 'product_id': self.product1.id, + 'product_uom': self.uom_unit.id, + 'product_uom_qty': 5.0, + }) + move2 = self.env['stock.move'].create({ + 'name': 'test_use_unreserved_move_line_1_1', + 'location_id': self.stock_location.id, + 'location_dest_id': self.customer_location.id, + 'product_id': self.product1.id, + 'product_uom': self.uom_unit.id, + 'product_uom_qty': 5.0, + }) + move1._action_confirm() + move1._action_assign() + move2._action_confirm() + move2._action_assign() + move3 = self.env['stock.move'].create({ + 'name': 'test_use_unreserved_move_line_1_1', + 'location_id': self.stock_location.id, + 'location_dest_id': self.customer_location.id, + 'product_id': self.product1.id, + 'product_uom': self.uom_unit.id, + 'product_uom_qty': 0.0, + 'quantity_done': 1.0, + }) + move3._action_confirm() + move3._action_assign() + move3._action_done() + self.assertEqual(move3.state, 'done') + quant = self.env['stock.quant']._gather(self.product1, self.stock_location) + self.assertEqual(quant.quantity, 9.0) + self.assertEqual(quant.reserved_quantity, 9.0) + def test_use_unreserved_move_line_1(self): """ Test that validating a stock move linked to an untracked product reserved by another one correctly unreserves the other one. diff --git a/addons/web/static/src/js/views/abstract_view.js b/addons/web/static/src/js/views/abstract_view.js index 9853f279794..a8e4ebbd8f0 100644 --- a/addons/web/static/src/js/views/abstract_view.js +++ b/addons/web/static/src/js/views/abstract_view.js @@ -273,3 +273,35 @@ var AbstractView = Class.extend({ return AbstractView; }); + +odoo.define('web.viewUtils', function () { +"use strict"; + +/** + * FIXME: move this module to its own file in master + */ + +var utils = { + /** + * States whether or not the quick create feature is available for the given + * datapoint, depending on its groupBy field. + * + * @param {Object} list dataPoint of type list + * @returns {Boolean} true iff the kanban quick create feature is available + */ + isQuickCreateEnabled: function (list) { + var groupByField = list.groupedBy[0] && list.groupedBy[0].split(':')[0]; + if (!groupByField) { + return false; + } + var availableTypes = ['char', 'boolean', 'many2one']; + if (!_.contains(availableTypes, list.fields[groupByField].type)) { + return false; + } + return true; + }, +}; + +return utils; + +}); diff --git a/addons/web/static/src/js/views/basic/basic_view.js b/addons/web/static/src/js/views/basic/basic_view.js index fc6dab9e1b1..93a88d27852 100644 --- a/addons/web/static/src/js/views/basic/basic_view.js +++ b/addons/web/static/src/js/views/basic/basic_view.js @@ -213,7 +213,7 @@ var BasicView = AbstractView.extend({ attrs.options = attrs.options ? pyeval.py_eval(attrs.options) : {}; } - if (attrs.on_change && !field.onChange) { + if (attrs.on_change && attrs.on_change !== "0" && !field.onChange) { field.onChange = "1"; } diff --git a/addons/web/static/src/js/views/kanban/kanban_controller.js b/addons/web/static/src/js/views/kanban/kanban_controller.js index 986fff3a53e..6f09381313c 100644 --- a/addons/web/static/src/js/views/kanban/kanban_controller.js +++ b/addons/web/static/src/js/views/kanban/kanban_controller.js @@ -12,6 +12,7 @@ var Context = require('web.Context'); var core = require('web.core'); var Domain = require('web.Domain'); var view_dialogs = require('web.view_dialogs'); +var viewUtils = require('web.viewUtils'); var _t = core._t; var qweb = core.qweb; @@ -34,12 +35,15 @@ var KanbanController = BasicController.extend({ /** * @override * @param {Object} params + * @param {boolean} params.quickCreateEnabled set to false to disable the + * quick create feature */ init: function (parent, model, renderer, params) { this._super.apply(this, arguments); this.on_create = params.on_create; this.hasButtons = params.hasButtons; + this.quickCreateEnabled = params.quickCreateEnabled; }, //-------------------------------------------------------------------------- @@ -259,8 +263,8 @@ var KanbanController = BasicController.extend({ */ _onButtonNew: function () { var state = this.model.get(this.handle, {raw: true}); - var hasColumns = state.groupedBy.length > 0 && state.data.length > 0; - if (hasColumns && this.on_create === 'quick_create') { + var quickCreateEnabled = this.quickCreateEnabled && viewUtils.isQuickCreateEnabled(state); + if (this.on_create === 'quick_create' && quickCreateEnabled) { // Activate the quick create in the first column this.renderer.addQuickCreate(); } else if (this.on_create && this.on_create !== 'quick_create') { 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 7b5d5879b81..e52b4e3c0d5 100644 --- a/addons/web/static/src/js/views/kanban/kanban_renderer.js +++ b/addons/web/static/src/js/views/kanban/kanban_renderer.js @@ -9,6 +9,7 @@ var ColumnQuickCreate = require('web.kanban_column_quick_create'); var QWeb = require('web.QWeb'); var session = require('web.session'); var utils = require('web.utils'); +var viewUtils = require('web.viewUtils'); var qweb = core.qweb; @@ -93,6 +94,9 @@ var KanbanRenderer = BasicRenderer.extend({ }), /** * @override + * @param {Object} params + * @param {boolean} params.quickCreateEnabled set to false to disable the + * quick create feature */ init: function (parent, state, params) { this._super.apply(this, arguments); @@ -111,6 +115,7 @@ var KanbanRenderer = BasicRenderer.extend({ if (this.columnOptions.hasProgressBar) { this.columnOptions.progressBarStates = {}; } + this.quickCreateEnabled = params.quickCreateEnabled; this._setState(state); }, /** @@ -423,6 +428,7 @@ var KanbanRenderer = BasicRenderer.extend({ groupedBy: groupByField, grouped_by_m2o: this.groupedByM2O, relation: relation, + quick_create: this.quickCreateEnabled && viewUtils.isQuickCreateEnabled(state), }); this.createColumnEnabled = this.groupedByM2O && this.columnOptions.group_creatable; }, diff --git a/addons/web/static/src/js/views/kanban/kanban_view.js b/addons/web/static/src/js/views/kanban/kanban_view.js index 99be6175da3..5661277705a 100644 --- a/addons/web/static/src/js/views/kanban/kanban_view.js +++ b/addons/web/static/src/js/views/kanban/kanban_view.js @@ -62,7 +62,6 @@ var KanbanView = BasicView.extend({ editable: activeActions.group_edit, deletable: activeActions.group_delete, group_creatable: activeActions.group_create && !config.device.isMobile, - quick_create: params.isQuickCreateEnabled || this._isQuickCreateEnabled(), quickCreateView: this.arch.attrs.quick_create_view || null, hasProgressBar: !!progressBar, }; @@ -71,6 +70,7 @@ var KanbanView = BasicView.extend({ deletable: activeActions.delete, read_only_mode: params.readOnlyMode, }; + this.rendererParams.quickCreateEnabled = this._isQuickCreateEnabled(); var examples = this.arch.attrs.examples; if (examples) { this.rendererParams.examples = kanbanExamplesRegistry.get(examples); @@ -79,6 +79,7 @@ var KanbanView = BasicView.extend({ this.controllerParams.on_create = this.arch.attrs.on_create; this.controllerParams.readOnlyMode = false; this.controllerParams.hasButtons = true; + this.controllerParams.quickCreateEnabled = this.rendererParams.quickCreateEnabled; if (config.device.isMobile) { this.jsLibs.push('/web/static/lib/jquery.touchSwipe/jquery.touchSwipe.js'); @@ -92,13 +93,10 @@ var KanbanView = BasicView.extend({ /** * @private * @param {Object} viewInfo + * @returns {boolean} true iff the quick create feature is not explicitely + * disabled (with create="False" or quick_create="False" in the arch) */ _isQuickCreateEnabled: function () { - var groupBy = this.loadParams.groupBy[0]; - groupBy = groupBy !== undefined ? groupBy.split(':')[0] : undefined; - if (groupBy !== undefined && !_.contains(['char', 'boolean', 'many2one'], this.fields[groupBy].type)) { - return false; - } if (!this.controllerParams.activeActions.create) { return false; } diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index ef1219a9a0b..49c3aa73b6e 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -790,7 +790,7 @@ - +
diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js index 200216d03e5..30290114e29 100644 --- a/addons/web/static/tests/views/kanban_tests.js +++ b/addons/web/static/tests/views/kanban_tests.js @@ -1218,6 +1218,81 @@ QUnit.module('Views', { kanban.destroy(); }); + QUnit.test('quick create record in grouped on date(time) field', function (assert) { + assert.expect(6); + + var kanban = createView({ + View: KanbanView, + model: 'partner', + data: this.data, + arch: '' + + '' + + '
' + + '
' + + '
', + groupBy: ['date'], + intercepts: { + switch_view: function (ev) { + assert.deepEqual(_.pick(ev.data, 'res_id', 'view_type'), { + res_id: undefined, + view_type: 'form', + }, "should trigger an event to open the form view (twice)"); + }, + }, + }); + + assert.strictEqual(kanban.$('.o_kanban_header .o_kanban_quick_add i').length, 0, + "quick create should be disabled when grouped on a date field"); + + // clicking on CREATE in control panel should not open a quick create + kanban.$buttons.find('.o-kanban-button-new').click(); + assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 0, + "should not have opened the quick create widget"); + + kanban.reload({groupBy: ['datetime']}); + + assert.strictEqual(kanban.$('.o_kanban_header .o_kanban_quick_add i').length, 0, + "quick create should be disabled when grouped on a datetime field"); + + // clicking on CREATE in control panel should not open a quick create + kanban.$buttons.find('.o-kanban-button-new').click(); + assert.strictEqual(kanban.$('.o_kanban_quick_create').length, 0, + "should not have opened the quick create widget"); + + kanban.destroy(); + }); + + QUnit.test('quick create record feature is properly enabled/disabled at reload', function (assert) { + assert.expect(3); + + var kanban = createView({ + View: KanbanView, + model: 'partner', + data: this.data, + arch: '' + + '' + + '
' + + '
' + + '
', + groupBy: ['foo'], + }); + + assert.strictEqual(kanban.$('.o_kanban_header .o_kanban_quick_add i').length, 3, + "quick create should be enabled when grouped on a char field"); + + kanban.reload({groupBy: ['date']}); + + assert.strictEqual(kanban.$('.o_kanban_header .o_kanban_quick_add i').length, 0, + "quick create should now be disabled (grouped on date field)"); + + kanban.reload({groupBy: ['bar']}); + + assert.strictEqual(kanban.$('.o_kanban_header .o_kanban_quick_add i').length, 2, + "quick create should be enabled again (grouped on boolean field)"); + + kanban.destroy(); + }); + QUnit.test('many2many_tags in kanban views', function (assert) { assert.expect(12); @@ -3568,7 +3643,7 @@ QUnit.module('Views', { '
' + '' + '', - groupBy: ['int_field'], + groupBy: ['foo'], mockRPC: function (route, args) { if (route === '/web/dataset/resequence') { return $.when(true); diff --git a/doc/cla/individual/rakete.md b/doc/cla/individual/rakete.md new file mode 100644 index 00000000000..72fa2bcea7c --- /dev/null +++ b/doc/cla/individual/rakete.md @@ -0,0 +1,11 @@ +Germany, 2017-11-24 + +I hereby agree to the terms of the Odoo Individual Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Andreas Raster lazor@affenbande.org https://github.com/rakete diff --git a/doc/cla/individual/zoggy444.md b/doc/cla/individual/zoggy444.md new file mode 100644 index 00000000000..46f8490f973 --- /dev/null +++ b/doc/cla/individual/zoggy444.md @@ -0,0 +1,11 @@ +FRANCE, 2018-09-26 + +I hereby agree to the terms of the Odoo Individual Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +zoggy 43614933+zoggy444@users.noreply.github.com https://github.com/zoggy444 diff --git a/doc/reference/views.rst b/doc/reference/views.rst index cc165285915..d35af9b7b36 100644 --- a/doc/reference/views.rst +++ b/doc/reference/views.rst @@ -852,7 +852,7 @@ attributes: ``quick_create`` whether it should be possible to create records without switching to the form view. By default, ``quick_create`` is enabled when the Kanban view is - grouped, and disabled when not. + grouped by many2one, char or boolean fields, and disabled when not. Set to ``true`` to always enable it, and to ``false`` to always disable it. diff --git a/odoo/addons/base/data/base_data.sql b/odoo/addons/base/data/base_data.sql index 30d6cc6cd89..5f907ad5b60 100644 --- a/odoo/addons/base/data/base_data.sql +++ b/odoo/addons/base/data/base_data.sql @@ -107,6 +107,7 @@ CREATE TABLE res_company ( name varchar NOT NULL, partner_id integer, currency_id integer, + sequence integer, create_date timestamp without time zone, primary key(id) ); diff --git a/odoo/addons/base/models/ir_actions.py b/odoo/addons/base/models/ir_actions.py index dd1fbfaf7f6..f500909d2e1 100644 --- a/odoo/addons/base/models/ir_actions.py +++ b/odoo/addons/base/models/ir_actions.py @@ -339,7 +339,7 @@ class IrActionsServer(models.Model): DEFAULT_PYTHON_CODE = """# Available variables: # - env: Odoo Environment on which the action is triggered # - model: Odoo Model of the record on which the action is triggered; is a void recordset -# - record: record on which the action is triggered; may be be void +# - record: record on which the action is triggered; may be void # - records: recordset of all records on which the action is triggered in multi-mode; may be void # - time, datetime, dateutil, timezone: useful Python libraries # - log: log(message, level='info'): logging function to record debug information in ir.logging table diff --git a/odoo/addons/base/models/ir_model.py b/odoo/addons/base/models/ir_model.py index aa30446d240..88795c19564 100644 --- a/odoo/addons/base/models/ir_model.py +++ b/odoo/addons/base/models/ir_model.py @@ -768,6 +768,7 @@ class IrModelFields(models.Model): 'index': bool(field.index), 'store': bool(field.store), 'copy': bool(field.copy), + 'on_delete': getattr(field, 'ondelete', None), 'related': ".".join(field.related) if field.related else None, 'readonly': bool(field.readonly), 'required': bool(field.required), diff --git a/odoo/service/server.py b/odoo/service/server.py index 4820b1e3257..034409b7c32 100644 --- a/odoo/service/server.py +++ b/odoo/service/server.py @@ -135,6 +135,8 @@ class FSWatcher(object): try: source = open(path, 'rb').read() + b'\n' compile(source, path, 'exec') + except FileNotFoundError: + _logger.error('autoreload: python code change detected, FileNotFound for %s', path) except SyntaxError: _logger.error('autoreload: python code change detected, SyntaxError in %s', path) else: