From 350a4ff861982173220e2df428534468bcc1ad0c Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Wed, 22 Aug 2018 09:32:41 +0200 Subject: [PATCH 01/12] [FIX] crm: fix API merge, other spot fix cc72b0c2fada765746ac5347b779c8e5d63f530c did not go far enough Sorry for it --- addons/crm/models/crm_lead.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index 3cec41d721e..3c44753c8c0 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -667,7 +667,7 @@ class Lead(FormatAddress, models.Model): partner_match_domain.append(('partner_id', '=', partner_id)) partner_match_domain = ['|'] * (len(partner_match_domain) - 1) + partner_match_domain if not partner_match_domain: - return [] + return self.env['crm.lead'] domain = partner_match_domain if not include_lost: domain += ['&', ('active', '=', True), ('probability', '<', 100)] From be4651cda39b91256bab190c671a21e8d458d7f4 Mon Sep 17 00:00:00 2001 From: Arnold Moyaux Date: Wed, 22 Aug 2018 11:39:27 +0200 Subject: [PATCH 02/12] [FIX] stock: compare quantities with different uom Usecase to reproduce: - Create a receipt for 1 dozen. - Mark as todo - Unlock and update initial demand to 3 dozen. The reserved quantity on move line is 36 dozen. It happens because reserved_availability and product_qty are compare. However reserved_availability is in move UoM and product_qty is in move's product UoM. This commit clean the code in order to compare field that use the same UoM. Close #26504 --- addons/stock/models/stock_move.py | 10 ++++++---- addons/stock/tests/test_move.py | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 0dac3ad176d..20575ba9a8b 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -897,11 +897,13 @@ class StockMove(models.Model): assigned_moves = self.env['stock.move'] partially_available_moves = self.env['stock.move'] for move in self.filtered(lambda m: m.state in ['confirmed', 'waiting', 'partially_available']): + missing_reserved_uom_quantity = move.product_uom_qty - move.reserved_availability + missing_reserved_quantity = move.product_uom._compute_quantity(missing_reserved_uom_quantity, move.product_id.uom_id, rounding_method='HALF-UP') if move.location_id.should_bypass_reservation()\ or move.product_id.type == 'consu': # create the move line(s) but do not impact quants if move.product_id.tracking == 'serial' and (move.picking_type_id.use_create_lots or move.picking_type_id.use_existing_lots): - for i in range(0, int(move.product_qty - move.reserved_availability)): + for i in range(0, int(missing_reserved_quantity)): self.env['stock.move.line'].create(move._prepare_move_line_vals(quantity=1)) else: to_update = move.move_line_ids.filtered(lambda ml: ml.product_uom_id == move.product_uom and @@ -912,16 +914,16 @@ class StockMove(models.Model): not ml.package_id and not ml.owner_id) if to_update: - to_update[0].product_uom_qty += move.product_qty - move.reserved_availability + to_update[0].product_uom_qty += missing_reserved_uom_quantity else: - self.env['stock.move.line'].create(move._prepare_move_line_vals(quantity=move.product_qty - move.reserved_availability)) + self.env['stock.move.line'].create(move._prepare_move_line_vals(quantity=missing_reserved_quantity)) assigned_moves |= move else: if not move.move_orig_ids: if move.procure_method == 'make_to_order': continue # If we don't need any quantity, consider the move assigned. - need = move.product_qty - move.reserved_availability + need = missing_reserved_quantity if float_is_zero(need, precision_rounding=move.product_id.uom_id.rounding): assigned_moves |= move continue diff --git a/addons/stock/tests/test_move.py b/addons/stock/tests/test_move.py index 422d61c2dbc..1ce26f22f1f 100644 --- a/addons/stock/tests/test_move.py +++ b/addons/stock/tests/test_move.py @@ -950,6 +950,27 @@ class StockMove(TransactionCase): move_partial._action_assign() self.assertEqual(move_partial.state, 'assigned') + def test_availability_9(self): + """ Test the assignment mechanism when the product quantity is increase + on a receipt move. + """ + move_receipt = self.env['stock.move'].create({ + 'name': 'test_receipt_edit', + 'location_id': self.supplier_location.id, + 'location_dest_id': self.stock_location.id, + 'product_id': self.product1.id, + 'product_uom': self.uom_dozen.id, + 'product_uom_qty': 1.0, + }) + + move_receipt._action_confirm() + move_receipt._action_assign() + self.assertEqual(move_receipt.state, 'assigned') + move_receipt.product_uom_qty = 3.0 + move_receipt._action_assign() + self.assertEqual(move_receipt.state, 'assigned') + self.assertEqual(move_receipt.move_line_ids.product_uom_qty, 3) + def test_unreserve_1(self): """ Check that unreserving a stock move sets the products reserved as available and set the state back to confirmed. From a66650a7fa4bdb8e728ee2edd4ac9109701704fc Mon Sep 17 00:00:00 2001 From: David Monjoie Date: Wed, 22 Aug 2018 15:50:41 +0200 Subject: [PATCH 03/12] [FIX] web: esc cancels kanban column quick create The feature was already declared in AbstractQuickCreate, but the ColumnQuickCreate object did not define a _cancel function. --- .../src/js/views/kanban/kanban_quick_create.js | 13 +++++++++++++ addons/web/static/tests/views/kanban_tests.js | 9 ++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/js/views/kanban/kanban_quick_create.js b/addons/web/static/src/js/views/kanban/kanban_quick_create.js index 216cf1bbe88..559f508e37e 100644 --- a/addons/web/static/src/js/views/kanban/kanban_quick_create.js +++ b/addons/web/static/src/js/views/kanban/kanban_quick_create.js @@ -266,6 +266,19 @@ var ColumnQuickCreate = AbstractQuickCreate.extend({ // Private //-------------------------------------------------------------------------- + /** + * Triggers up an event to cancel the quick creation + * + * @override + * @private + */ + _cancel: function () { + if (!this.folded) { + this.folded = true; + this.$input.val(''); + this._update(); + } + }, /** * Triggers up an event to quick create a column with the given value * diff --git a/addons/web/static/tests/views/kanban_tests.js b/addons/web/static/tests/views/kanban_tests.js index 759ed61a00f..9638d1c20d3 100644 --- a/addons/web/static/tests/views/kanban_tests.js +++ b/addons/web/static/tests/views/kanban_tests.js @@ -1203,7 +1203,7 @@ QUnit.module('Views', { }); QUnit.test('quick create record & column in grouped on m2o', function (assert) { - assert.expect(2); + assert.expect(4); var kanban = createView({ View: KanbanView, @@ -1230,6 +1230,13 @@ QUnit.module('Views', { assert.strictEqual(kanban.$('.o_kanban_group:last span:contains(new column)').length, 1, "the last column should be the newly created one"); + + assert.strictEqual(kanban.$('.o_kanban_quick_create:visible button.o_kanban_add').length, 1, + "the quick create column should still be opened for mass creation"); + kanban.$('.o_column_quick_create input').trigger($.Event('keydown', { keyCode: $.ui.keyCode.ESCAPE })); + assert.strictEqual(kanban.$('.o_kanban_quick_create:visible button.o_kanban_add').length, 0, + "the quick create column should now be closed"); + kanban.destroy(); }); From 81f975d3f48239ee0722fb3892112db6915e7964 Mon Sep 17 00:00:00 2001 From: Toufik Benjaa Date: Wed, 22 Aug 2018 17:59:22 +0200 Subject: [PATCH 04/12] [FIX] mail: channel_get return wrong channel when duplicate partners - In some cases (merge of partners, etc ...) there might be mail.channel that have two mail.channel.partner with the same res.partner. When you try to open a discuss window with a partner for which you have no existing mail.channel. The channel opened is the one where your partner is duplicated. This is due to the fact that the SQL request to find the mail.channel doesn't take in account that a mail.channel may have twice the same partner. To fix this issue, we try to exactly match the partners we search. --- addons/mail/models/mail_channel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py index 91610463e81..372fc8ef0ea 100644 --- a/addons/mail/models/mail_channel.py +++ b/addons/mail/models/mail_channel.py @@ -361,8 +361,8 @@ class Channel(models.Model): AND P.partner_id IN %s AND channel_type LIKE 'chat' GROUP BY P.channel_id - HAVING COUNT(P.partner_id) = %s - """, (tuple(partners_to), len(partners_to),)) + HAVING array_agg(P.partner_id ORDER BY P.partner_id) = %s + """, (tuple(partners_to), sorted(list(partners_to)),)) result = self.env.cr.dictfetchall() if result: # get the existing channel between the given partners From 7ea67cea2e3897f2168fafb21ad8fb7160ef038e Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Tue, 21 Aug 2018 16:40:59 +0200 Subject: [PATCH 05/12] [FIX] crm: make sure there is a create date when computing date_open Display the field date_open in the crm.lead form view Create a new record Before this commit, there was a crash because since the record is a virtual one there is no create_date, hence the crash After this commit, we make sure we select records that have a create_date, and there is no crash OPW 1877080 closes #26499 --- addons/crm/models/crm_lead.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index 4feb5c3607a..644429bcf0a 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -166,7 +166,7 @@ class Lead(models.Model): @api.depends('date_open') def _compute_day_open(self): """ Compute difference between create date and open date """ - for lead in self.filtered(lambda l: l.date_open): + for lead in self.filtered(lambda l: l.date_open and l.create_date): date_create = fields.Datetime.from_string(lead.create_date) date_open = fields.Datetime.from_string(lead.date_open) lead.day_open = abs((date_open - date_create).days) @@ -174,7 +174,7 @@ class Lead(models.Model): @api.depends('date_closed') def _compute_day_close(self): """ Compute difference between current date and log date """ - for lead in self.filtered(lambda l: l.date_closed): + for lead in self.filtered(lambda l: l.date_closed and l.create_date): date_create = fields.Datetime.from_string(lead.create_date) date_close = fields.Datetime.from_string(lead.date_closed) lead.day_close = abs((date_close - date_create).days) From 5830fcf506eb66de3ef9dec388853ba2e9053625 Mon Sep 17 00:00:00 2001 From: Hiral Bhavsar Date: Mon, 26 Feb 2018 18:33:20 +0530 Subject: [PATCH 06/12] [FIX] account: reconciliation: Pass context properly when making RPC call This way, language formatting is always performed (123.30 becomes 123,30 in French for example). --- .../static/src/js/reconciliation/reconciliation_model.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js index c6e0bedb8ee..4f2e183cef9 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_model.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js @@ -445,6 +445,7 @@ var StatementModel = BasicModel.extend({ model: 'account.bank.statement.line', method: 'get_data_for_reconciliation_widget', args: [ids, excluded_ids], + context: self.context, }) .then(self._formatLine.bind(self)); }, @@ -1075,6 +1076,7 @@ var StatementModel = BasicModel.extend({ model: 'account.bank.statement.line', method: 'get_move_lines_for_reconciliation_widget', args: [line.id, line.st_line.partner_id, excluded_ids, filter, offset, limit], + context: this.context, }) .then(this._formatMoveLine.bind(this, handle)); }, @@ -1143,6 +1145,7 @@ var ManualModel = StatementModel.extend({ */ load: function (context) { var self = this; + this.context = context; var domain_account_id = []; if (context && context.company_ids) { @@ -1433,6 +1436,7 @@ var ManualModel = StatementModel.extend({ model: 'account.move.line', method: 'get_move_lines_for_manual_reconciliation', args: args, + context: this.context, }) .then(this._formatMoveLine.bind(this, handle)); }, From d6ee2abb987016c28e24f471f4b3d413af84c185 Mon Sep 17 00:00:00 2001 From: Swapnesh Shah Date: Tue, 24 Apr 2018 01:35:14 +0530 Subject: [PATCH 07/12] [FIX] point_of_sale: force invoice journal if enabled When the option "iface_invoicing" is enabled, an account journal must be set. Closes #24380 --- addons/point_of_sale/views/pos_config_view.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/point_of_sale/views/pos_config_view.xml b/addons/point_of_sale/views/pos_config_view.xml index 9ce98719c89..8092c6e5176 100644 --- a/addons/point_of_sale/views/pos_config_view.xml +++ b/addons/point_of_sale/views/pos_config_view.xml @@ -435,7 +435,8 @@
From 12fbbacc6d291f39484f9f675b6c80b52a8fcb55 Mon Sep 17 00:00:00 2001 From: Olivier Colson Date: Thu, 23 Aug 2018 14:52:31 +0200 Subject: [PATCH 08/12] [IMP] account: reconciliation widget: format dates depending on user's language preferences. --- addons/account/models/account_bank_statement.py | 4 ++-- addons/account/models/account_move.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index dbe70076c42..54a61a986e6 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -4,7 +4,7 @@ from odoo import api, fields, models, _ from odoo.osv import expression from odoo.tools import float_is_zero, pycompat from odoo.tools import float_compare, float_round, float_repr -from odoo.tools.misc import formatLang +from odoo.tools.misc import formatLang, format_date from odoo.exceptions import UserError, ValidationError import time @@ -512,7 +512,7 @@ class AccountBankStatementLine(models.Model): 'ref': self.ref, 'note': self.note or "", 'name': self.name, - 'date': self.date, + 'date': format_date(self.env, self.date), 'amount': amount, 'amount_str': amount_str, # Amount in the statement line currency 'currency_id': self.currency_id.id or statement_currency.id, diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 993a04abb7f..841b7d5d84d 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -5,7 +5,7 @@ from collections import OrderedDict from odoo import api, fields, models, _ from odoo.osv import expression from odoo.exceptions import RedirectWarning, UserError, ValidationError -from odoo.tools.misc import formatLang +from odoo.tools.misc import formatLang, format_date from odoo.tools import float_is_zero, float_compare from odoo.tools.safe_eval import safe_eval from odoo.addons import decimal_precision as dp @@ -789,8 +789,8 @@ class AccountMoveLine(models.Model): 'account_code': line.account_id.code, 'account_name': line.account_id.name, 'account_type': line.account_id.internal_type, - 'date_maturity': line.date_maturity, - 'date': line.date, + 'date_maturity': format_date(self.env, line.date_maturity), + 'date': format_date(self.env, line.date), 'journal_id': [line.journal_id.id, line.journal_id.display_name], 'partner_id': line.partner_id.id, 'partner_name': line.partner_id.name, From 01b1830219ba617079561fce6b958e271a0df355 Mon Sep 17 00:00:00 2001 From: Nicolas Lempereur Date: Wed, 22 Aug 2018 16:52:15 +0200 Subject: [PATCH 09/12] [FIX] mail: access token in received cid images Following 50ab18176 an access token is added in the URL, but there may not be an existing access token. If so this commit generate one. opw-1877347 closes #26526 --- addons/mail/models/mail_thread.py | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index deb9165a6f4..fa819f038b1 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -1770,6 +1770,7 @@ class MailThread(models.AbstractModel): if not attachment: attachment = fname_mapping.get(node.get('data-filename'), '') if attachment: + attachment.generate_access_token() node.set('src', '/web/image/%s?access_token=%s' % (attachment.id, attachment.access_token)) postprocessed = True if postprocessed: From a19d2c98f4c80d162a92d08ce4a43ca6021f3bca Mon Sep 17 00:00:00 2001 From: Invitu Date: Tue, 19 Jun 2018 20:10:54 -1000 Subject: [PATCH 10/12] [FIX] point_of_sale: consider timezone in return orders Similar to 21575407 for returns payment_date is a date field, make sure the day is the one of the user Closes #25342 --- addons/point_of_sale/models/pos_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index fa1dca0461b..51fdad1610b 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -143,7 +143,7 @@ class PosOrder(models.Model): cash_journal_id = cash_journal[0].id order.add_payment({ 'amount': -pos_order['amount_return'], - 'payment_date': fields.Datetime.now(), + 'payment_date': fields.Date.context_today(self), 'payment_name': _('return'), 'journal': cash_journal_id, }) From b50958c40f943df44bd2cf9fe4d460a1438b17c5 Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Wed, 22 Aug 2018 15:52:33 +0200 Subject: [PATCH 11/12] [FIX] account: allow range amount search for reconciliation widget In the reconciliation widget, search for an amount like 5361.61 Before this commit, if the targetted line that you want to see was represented as 5361.61000001 you did not see it in the results of the search After this commit, you do! OPW 1872543 closes #26523 --- addons/account/models/account_move.py | 37 +++++++++++++++++++++------ 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index e7d5539bd65..a035ebd7db1 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -608,6 +608,21 @@ class AccountMoveLine(models.Model): :param excluded_ids: list of ids of move lines that should not be fetched :param str: search string """ + epsilon = 0.0001 + + def _domain_range_amount(field, amount, signed=False): + def build_for_amount(amount): + return expression.AND([ + [(field, '>=', amount - epsilon)], + [(field, '<=', amount + epsilon)] + ]) + + unsigned_domain = build_for_amount(amount) + if not signed: + return unsigned_domain + + return expression.OR([unsigned_domain, build_for_amount(-amount)]) + context = (self._context or {}) if excluded_ids is None: excluded_ids = [] @@ -624,14 +639,20 @@ class AccountMoveLine(models.Model): ] try: amount = float(str) - amount_domain = [ - '|', ('amount_residual', '=', amount), - '|', ('amount_residual_currency', '=', amount), - '|', ('amount_residual', '=', -amount), - '|', ('amount_residual_currency', '=', -amount), - '&', ('account_id.internal_type', '=', 'liquidity'), - '|', '|', ('debit', '=', amount), ('credit', '=', amount), ('amount_currency', '=', amount), - ] + residual_domain = expression.OR([ + _domain_range_amount(field, amount, True) + for field in ('amount_residual', 'amount_residual_currency') + ]) + + liquidity_domain = expression.AND([ + [('account_id.internal_type', '=', 'liquidity')], + expression.OR([ + _domain_range_amount(field, amount) + for field in ('debit', 'credit', 'amount_currency') + ]) + ]) + + amount_domain = expression.OR([residual_domain, liquidity_domain]) str_domain = expression.OR([str_domain, amount_domain]) except: pass From 818c18e55d0718286ff5bf332186a10f4d7a58ef Mon Sep 17 00:00:00 2001 From: len-odoo Date: Tue, 24 Jul 2018 15:26:41 +0200 Subject: [PATCH 12/12] [FIX] web: fix js data during consecutive sequence of resequence moves Let x_1, ..., x_a, x_{a+1}, ..., x_{a+k}, x_{a+k+1}, ..., x_{n} be a record sequence ordered by its 'sequence' field. Suppose the reordering occurs between elements x_{a+k} and x_{a+1}. If the subsequence x_1, ..., x_a is ordered, then it does not change. This is also true of the subsequence x_{a+k+1}, ..., x_{n}. The resquence function makes exactly this assumption, and only sends the server the subsequence x_{a+1}, ..., x_{a+k} to update its 'sequence' field. The update of x is done as follows: it is index + offset, where index is the position of x in the subsequence, and offset is the 'sequence' of the first element of the sequence (i.e. x_{a+1}.sequence). It is easy to see that we need another hypothesis: that the sequence numbers are unique. To show it, suppose that the sequence of x_1, ..., x_a is 0, then all x_{a+1}, ..., x_{a+k} have sequence 1, and lastly all x_{a+k}, x_{a+k+1}, ..., x_{n} have sequence 2. Then after the call to the server resequence, the subsequence x_{a+1}, ..., x_{a+k} would have sequence numbers 1, 2,..., k. Since the x_{a+k+1}, ..., x_{n} subsequence have 'sequence' value 1, this would obviously be incorrect. We fix the reordering of the data in the js frontend to be consistent with what is done by the server. Note that this is only pertinent for sequences of reordering moves, since otherwise the data comes directly from the server. opw 1867049 --- .../static/src/js/views/basic/basic_model.js | 8 +- .../web/static/tests/helpers/mock_server.js | 19 ++- addons/web/static/tests/views/list_tests.js | 110 +++++++++++++++++- 3 files changed, 132 insertions(+), 5 deletions(-) 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 7621fe35446..16f77b7954c 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -894,8 +894,14 @@ var BasicModel = AbstractModel.extend({ params: params, }) .then(function () { + var offset = options.offset ? options.offset : 0; + var old_data = data.data.slice(); data.data = _.sortBy(data.data, function (d) { - return _.indexOf(resIDs, self.localData[d].res_id); + if (_.contains(resIDs, self.localData[d].res_id)) { + return _.indexOf(resIDs, self.localData[d].res_id) + offset; + } else { + return _.indexOf(old_data, d); + } }); data.res_ids = []; _.each(data.data, function (d) { diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js index 4ae38143225..ede17917cca 100644 --- a/addons/web/static/tests/helpers/mock_server.js +++ b/addons/web/static/tests/helpers/mock_server.js @@ -763,6 +763,23 @@ var MockServer = Class.extend({ return data; }, + /** + * Simulates a 'resequence' operation + * + * @private + * @param {string} model + * @param {string} field + * @param {Array} ids + */ + _mockResequence: function (args) { + var offset = args.offset ? Number(args.offset) : 0; + var field = args.field ? args.field : 'sequence'; + var records = this.data[args.model].records; + for (var i in args.ids) { + var record = _.findWhere(this.data[args.model].records, {id: args.ids[i]}); + record[field] = Number(i) + offset; + } + }, /** * Simulate a 'search_count' operation * @@ -922,7 +939,7 @@ var MockServer = Class.extend({ return $.when(this._mockSearchReadController(args)); case '/web/dataset/resequence': - return $.when(); + return $.when(this._mockResequence(args)); } if (route.indexOf('/web/image') >= 0 || _.contains(['.png', '.jpg'], route.substr(route.length - 4))) { return $.when(); diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index 1330f84c32c..eebf09c9365 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -3019,20 +3019,118 @@ QUnit.module('Views', { list.destroy(); }); + QUnit.test('result of consecutive resequences is correctly sorted', function (assert) { + assert.expect(9); + this.data = { // we want the data to be minimal to have a minimal test + foo: { + fields: {int_field: {string: "int_field", type: "integer", sortable: true}}, + records: [ + {id: 1, int_field: 0}, + {id: 2, int_field: 1}, + {id: 3, int_field: 2}, + {id: 4, int_field: 3}, + ] + } + }; + var moves = 0; + var list = createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '' + + '' + + '' + + '', + mockRPC: function (route, args) { + if (route === '/web/dataset/resequence') { + if (moves === 0) { + assert.deepEqual(args, { + model: "foo", + ids: [4, 3], + offset: 2, + field: "int_field", + }); + } + if (moves === 1) { + assert.deepEqual(args, { + model: "foo", + ids: [1, 4, 2, 3], + field: "int_field", + }); + } + if (moves === 2) { + assert.deepEqual(args, { + model: "foo", + ids: [2, 4], + offset: 1, + field: "int_field", + }); + } + if (moves === 3) { + assert.deepEqual(args, { + model: "foo", + ids: [1, 4, 2, 3], + field: "int_field", + }); + } + moves += 1; + } + return this._super.apply(this, arguments); + }, + }); + assert.strictEqual(list.$('tbody tr td').text(), '1234', + "default should be sorted by id"); + testUtils.dragAndDrop( + list.$('.ui-sortable-handle').eq(3), + list.$('tbody tr').eq(2), + {position: 'top'} + ); + assert.strictEqual(list.$('tbody tr td').text(), '1243', + "the int_field (sequence) should have been correctly updated"); + testUtils.dragAndDrop( + list.$('.ui-sortable-handle').eq(2), + list.$('tbody tr').eq(1), + {position: 'top'} + ); + assert.deepEqual(list.$('tbody tr td').text(), '1423', + "the int_field (sequence) should have been correctly updated"); + testUtils.dragAndDrop( + list.$('.ui-sortable-handle').eq(1), + list.$('tbody tr').eq(3), + {position: 'top'} + ); + assert.deepEqual(list.$('tbody tr td').text(), '1243', + "the int_field (sequence) should have been correctly updated"); + testUtils.dragAndDrop( + list.$('.ui-sortable-handle').eq(2), + list.$('tbody tr').eq(1), + {position: 'top'} + ); + assert.deepEqual(list.$('tbody tr td').text(), '1423', + "the int_field (sequence) should have been correctly updated"); + list.destroy(); + }); + QUnit.test('editable list with handle widget', function (assert) { assert.expect(12); + // resequence makes sense on a sequence field, not on arbitrary fields + this.data.foo.records[0].int_field = 0; + this.data.foo.records[1].int_field = 1; + this.data.foo.records[2].int_field = 2; + this.data.foo.records[3].int_field = 3; + var list = createView({ View: ListView, model: 'foo', data: this.data, - arch: '' + + arch: '' + '' + '' + '', mockRPC: function (route, args) { if (route === '/web/dataset/resequence') { - assert.strictEqual(args.offset, -4, + assert.strictEqual(args.offset, 1, "should write the sequence starting from the lowest current one"); assert.strictEqual(args.field, 'int_field', "should write the right field as sequence"); @@ -3080,6 +3178,12 @@ QUnit.module('Views', { QUnit.test('editable list with handle widget with slow network', function (assert) { assert.expect(15); + // resequence makes sense on a sequence field, not on arbitrary fields + this.data.foo.records[0].int_field = 0; + this.data.foo.records[1].int_field = 1; + this.data.foo.records[2].int_field = 2; + this.data.foo.records[3].int_field = 3; + var def = $.Deferred(); var list = createView({ @@ -3092,7 +3196,7 @@ QUnit.module('Views', { '', mockRPC: function (route, args) { if (route === '/web/dataset/resequence') { - assert.strictEqual(args.offset, -4, + assert.strictEqual(args.offset, 1, "should write the sequence starting from the lowest current one"); assert.strictEqual(args.field, 'int_field', "should write the right field as sequence");