diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index a4573fbee55..3b72e38a2af 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -499,7 +499,6 @@ class AccountBankStatementLine(models.Model): 'st_line': st_line.get_statement_line_for_reconciliation_widget(), 'reconciliation_proposition': rp }) - return ret def get_statement_line_for_reconciliation_widget(self): diff --git a/addons/account/static/src/js/reconciliation/reconciliation_action.js b/addons/account/static/src/js/reconciliation/reconciliation_action.js index 51175be7794..8763d5acaa1 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_action.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_action.js @@ -30,6 +30,7 @@ var StatementAction = Widget.extend(ControlPanelMixin, { validate_all_balanced: '_onValidate', change_name: '_onChangeName', close_statement: '_onCloseStatement', + load_more: '_onLoadMore', }, config: { // used to instanciate the model @@ -71,6 +72,7 @@ var StatementAction = Widget.extend(ControlPanelMixin, { 'bank_statement_id': self.model.bank_statement_id, 'valuenow': self.model.valuenow, 'valuemax': self.model.valuemax, + 'defaultDisplayQty': self.model.defaultDisplayQty, 'title': self.title, }); }); @@ -89,12 +91,7 @@ var StatementAction = Widget.extend(ControlPanelMixin, { this.update_control_panel({breadcrumbs: breadcrumbs, search_view_hidden: true}, {clear: true}); this.renderer.prependTo(self.$('.o_form_sheet')); - _.each(this.model.lines, function (line, handle) { - var widget = new self.config.LineRenderer(self, self.model, line); - widget.handle = handle; - self.widgets.push(widget); - widget.appendTo(self.$('.o_reconciliation_lines')); - }); + this._renderLines(); this._openFirstLine(); }, @@ -134,6 +131,15 @@ var StatementAction = Widget.extend(ControlPanelMixin, { return _.find(this.widgets, function (widget) {return widget.handle===handle;}); }, + /** + * + */ + _loadMore: function(qty) { + var self = this; + return this.model.loadMore(qty).then(function () { + self._renderLines(); + }); + }, /** * sitch to 'match' the first available line * @@ -153,6 +159,24 @@ var StatementAction = Widget.extend(ControlPanelMixin, { } return handle; }, + /** + * render line widget and append to view + * + * @private + */ + _renderLines: function () { + var self = this; + var linesToDisplay = this.model.getStatementLines(); + _.each(linesToDisplay, function (line, handle) { + var widget = new self.config.LineRenderer(self, self.model, line); + widget.handle = handle; + self.widgets.push(widget); + widget.appendTo(self.$('.o_reconciliation_lines')); + }); + if (this.model.hasMoreLines() === false) { + this.renderer.hideLoadMoreButton(); + } + }, //-------------------------------------------------------------------------- // Handlers @@ -223,6 +247,14 @@ var StatementAction = Widget.extend(ControlPanelMixin, { }); }); }, + /** + * Load more statement and render them + * + * @param {OdooEvent} event + */ + _onLoadMore: function (event) { + return this._loadMore(this.model.defaultDisplayQty); + }, /** * call 'validate' or 'autoReconciliation' model method then destroy the * validated lines and update the action renderer with the new status bar @@ -246,7 +278,15 @@ var StatementAction = Widget.extend(ControlPanelMixin, { }); _.each(result.handles, function (handle) { self._getWidget(handle).destroy(); + var index = _.findIndex(self.widgets, function (widget) {return widget.handle===handle;}); + self.widgets.splice(index, 1); }); + // Get number of widget and if less than constant and if there are more to laod, load until constant + if (self.widgets.length < self.model.defaultDisplayQty + && self.model.valuemax - self.model.valuenow >= self.model.defaultDisplayQty) { + var toLoad = self.model.defaultDisplayQty - self.widgets.length; + self._loadMore(toLoad); + } self._openFirstLine(); }); }, diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js index e12f8f21009..7d3297a7003 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_model.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js @@ -100,6 +100,8 @@ var StatementModel = BasicModel.extend({ this.lines = {}; this.valuenow = 0; this.valuemax = 0; + this.alreadyDisplayed = []; + this.defaultDisplayQty = 10; }, //-------------------------------------------------------------------------- @@ -283,6 +285,34 @@ var StatementModel = BasicModel.extend({ getContext: function () { return this.context; }, + /** + * Return the lines that needs to be displayed by the widget + * + * @returns {Object} lines that are loaded and not yet displayed + */ + getStatementLines: function () { + var self = this; + var linesToDisplay = _.pick(this.lines, function(value, key, object) { + if (value.visible === true && self.alreadyDisplayed.indexOf(key) === -1) { + self.alreadyDisplayed.push(key); + return object; + } + }); + return linesToDisplay; + }, + /** + * Return a boolean telling if load button needs to be displayed or not + * + * @returns {boolean} true if load more button needs to be displayed + */ + hasMoreLines: function () { + var self = this; + var notDisplayed = _.filter(this.lines, function(line) { return !line.visible; }); + if (notDisplayed.length > 0) { + return true; + } + return false; + }, /** * get the line data for this handle * @@ -355,14 +385,42 @@ var StatementModel = BasicModel.extend({ line.reconcileModels = self.reconcileModels; }); var ids = _.pluck(self.lines, 'id'); - return self._rpc({ - model: 'account.bank.statement.line', - method: 'get_data_for_reconciliation_widget', - args: [ids], - }) - .then(self._formatLine.bind(self)); + ids = ids.splice(0, self.defaultDisplayQty); + self.pagerIndex = ids.length; + return self.loadData(ids, []); }); }, + /** + * Load more bank statement line + * + * @param {integer} quantity to load + * @returns {Deferred} + */ + loadMore: function(qty) { + if (qty === undefined) { + qty = this.defaultDisplayQty; + } + var ids = _.pluck(this.lines, 'id'); + ids = ids.splice(this.pagerIndex, qty); + this.pagerIndex += qty; + return this.loadData(ids, this._getExcludedIds()); + }, + /** + * RPC method to load informations on lines + * + * @param {Array} ids of bank statement line passed to rpc call + * @param {Array} list of move_line ids that needs to be excluded from search + * @returns {Deferred} + */ + loadData: function(ids, excluded_ids) { + var self = this; + return self._rpc({ + model: 'account.bank.statement.line', + method: 'get_data_for_reconciliation_widget', + args: [ids, excluded_ids], + }) + .then(self._formatLine.bind(self)); + }, /** * Add lines into the propositions from the reconcile model * Can add 2 lines, and each with its taxes. The second line become editable @@ -770,6 +828,7 @@ var StatementModel = BasicModel.extend({ var line = _.find(self.lines, function (l) { return l.id === data.st_line.id; }); + line.visible = true; _.extend(line, data); self._formatLineProposition(line, line.reconciliation_proposition); if (!line.reconciliation_proposition.length) { @@ -850,6 +909,26 @@ var StatementModel = BasicModel.extend({ prop.amount = prop.base_amount; return prop; }, + /** + * Return list of account_move_line that has been selected and needs to be removed + * from other calls. + * + * @private + * @returns {Array} list of excluded ids + */ + _getExcludedIds: function () { + var excludedIds = []; + _.each(this.lines, function(line) { + if (line.reconciliation_proposition) { + _.each(line.reconciliation_proposition, function(prop) { + if (parseInt(prop['id'])) { + excludedIds.push(prop['id']); + } + }) + } + }); + return excludedIds; + }, /** * Defined whether the line is to be displayed or not. Here, we only display * the line if it comes from the server or if an account is defined when it @@ -1168,7 +1247,8 @@ var ManualModel = StatementModel.extend({ 'filter': "", 'reconcileModels': [], 'account_id': this._formatNameGet([data.account_id, data.account_name]), - 'st_line': data + 'st_line': data, + 'visible': true }); this._formatLineProposition(line, line.reconciliation_proposition); if (!line.reconciliation_proposition.length) { diff --git a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js index 915dd2c7966..d750f73503d 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js @@ -24,6 +24,7 @@ var StatementRenderer = Widget.extend(FieldManagerMixin, { "click *[rel='do_action']": "_onDoAction", 'click button.button_back_to_statement': '_onGoToBankStatement', 'click button.button_close_statement': '_onCloseBankStatement', + 'click button.js_load_more': '_onLoadMore', }, /** * @override @@ -87,6 +88,12 @@ var StatementRenderer = Widget.extend(FieldManagerMixin, { //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- + /* + * hide the button to load more statement line + */ + hideLoadMoreButton: function () { + this.$('.js_load_more').hide(); + }, /** * update the statement rendering * @@ -207,6 +214,14 @@ var StatementRenderer = Widget.extend(FieldManagerMixin, { view_mode: 'form', }); }, + /** + * Load more statement lines for reconciliation + * @private + * @param {MouseEvent} event + */ + _onLoadMore: function (e) { + this.trigger_up('load_more'); + }, /** * @private */ diff --git a/addons/account/static/src/xml/account_reconciliation.xml b/addons/account/static/src/xml/account_reconciliation.xml index 0023fc28b8c..da27c44d882 100644 --- a/addons/account/static/src/xml/account_reconciliation.xml +++ b/addons/account/static/src/xml/account_reconciliation.xml @@ -25,6 +25,9 @@
+
+ +
Tip: Hit CTRL-Enter to reconcile all the balanced items in the sheet.
diff --git a/addons/account/static/tests/reconciliation_tests.js b/addons/account/static/tests/reconciliation_tests.js index 4c6ede6eb6e..c5254bfc69a 100644 --- a/addons/account/static/tests/reconciliation_tests.js +++ b/addons/account/static/tests/reconciliation_tests.js @@ -1,7 +1,9 @@ odoo.define('account.reconciliation_tests.data', function () { "use strict"; -var data = { +var Datas = {}; + +var db = { 'res.partner': { fields: { id: {string: "ID", type: 'integer'}, @@ -58,7 +60,7 @@ var data = { {id: 7, display_name: "Tax 10.00% include", amount: 10, price_include: true, account_id: 288}, ], json_friendly_compute_all: function (args) { - var tax = _.find(data['account.tax'].records, {'id': args[0][0]}); + var tax = _.find(db['account.tax'].records, {'id': args[0][0]}); var amount = args[1]; var tax_base = tax.price_include ? amount*100/(100+tax.amount) : amount; return $.when({ @@ -103,7 +105,7 @@ var data = { 'account.bank.statement': { fields: {}, reconciliation_widget_preprocess: function () { - return $.when(data_preprocess); + return $.when(Datas.used.data_preprocess); }, }, 'account.bank.statement.line': { @@ -122,19 +124,19 @@ var data = { var partner_id = args.splice(1, 1)[0]; var excluded_ids = args.splice(1, 1)[0]; var key = JSON.stringify(args); - if (!mv_lines[key]) { + if (!Datas.used.mv_lines[key]) { throw new Error("Unknown parameters for get_move_lines_for_reconciliation_widget: '"+ key + "'"); } - return $.when(_.filter(mv_lines[key], function (line) { + return $.when(_.filter(Datas.used.mv_lines[key], function (line) { return excluded_ids.indexOf(line.id) === -1 && (!partner_id || partner_id === line.partner_id); })); }, get_data_for_reconciliation_widget: function (args) { var ids = args[0]; - return $.when(_.filter(data_widget, function (w) {return _.contains(ids, w.st_line.id);})); + return $.when(_.filter(Datas.used.data_widget, function (w) {return _.contains(ids, w.st_line.id);})); }, reconciliation_widget_auto_reconcile: function () { - return $.when(auto_reconciliation); + return $.when(Datas.used.auto_reconciliation); }, process_reconciliations: function (args) { var datas = args[1]; @@ -142,8 +144,8 @@ var data = { ids = ids.concat(_.flatten(_.pluck(datas, 'payment_aml_ids'))); ids = _.compact(ids); - for (var key in move_lines_for_manual_reconciliation) { - move_lines_for_manual_reconciliation[key] = _.filter(move_lines_for_manual_reconciliation[key], function (mv_line) { + for (var key in Datas.used.move_lines_for_manual_reconciliation) { + Datas.used.move_lines_for_manual_reconciliation[key] = _.filter(Datas.used.move_lines_for_manual_reconciliation[key], function (mv_line) { return ids.indexOf(mv_line.id) === -1; }); } @@ -154,18 +156,18 @@ var data = { fields: {}, get_data_for_manual_reconciliation_widget: function (args) { var key = JSON.stringify(args); - if (!data_for_manual_reconciliation_widget[key]) { + if (!Datas.used.data_for_manual_reconciliation_widget[key]) { throw new Error("Unknown parameters for get_data_for_manual_reconciliation_widget: '"+ key + "'"); } - return $.when(data_for_manual_reconciliation_widget[key]); + return $.when(Datas.used.data_for_manual_reconciliation_widget[key]); }, get_move_lines_for_manual_reconciliation: function (args) { var excluded_ids = args.splice(2, 1)[0]; var key = JSON.stringify(args); - if (!move_lines_for_manual_reconciliation[key]) { + if (!Datas.used.move_lines_for_manual_reconciliation[key]) { throw new Error("Unknown parameters for get_move_lines_for_manual_reconciliation: '"+ key + "'"); } - return $.when(_.filter(move_lines_for_manual_reconciliation[key], function (line) { + return $.when(_.filter(Datas.used.move_lines_for_manual_reconciliation[key], function (line) { return excluded_ids.indexOf(line.id) === -1; })); }, @@ -174,8 +176,8 @@ var data = { var datas = args[0]; for (var i in datas) { var data = datas[i]; - for (var key in move_lines_for_manual_reconciliation) { - move_lines_for_manual_reconciliation[key] = _.filter(move_lines_for_manual_reconciliation[key], function (mv_line) { + for (var key in Datas.used.move_lines_for_manual_reconciliation) { + Datas.used.move_lines_for_manual_reconciliation[key] = _.filter(Datas.used.move_lines_for_manual_reconciliation[key], function (mv_line) { return data.mv_line_ids.indexOf(mv_line.id) === -1; }); } @@ -468,23 +470,22 @@ var options = { } }; -return { - params: { - data: data, - data_preprocess: data_preprocess, - data_widget: data_widget, - mv_lines: mv_lines, - auto_reconciliation: auto_reconciliation, - data_for_manual_reconciliation_widget: data_for_manual_reconciliation_widget, - move_lines_for_manual_reconciliation: move_lines_for_manual_reconciliation, - session: session, - options: options, - }, - // this is the main function for this module. Its job is to export (and clone) all data for a test. - getParams: function () { - return $.extend(true, {}, this.params); - } +Datas.params = { + data: db, + data_preprocess: data_preprocess, + data_widget: data_widget, + mv_lines: mv_lines, + auto_reconciliation: auto_reconciliation, + data_for_manual_reconciliation_widget: data_for_manual_reconciliation_widget, + move_lines_for_manual_reconciliation: move_lines_for_manual_reconciliation, + session: session, + options: options, }; + // this is the main function for this module. Its job is to export (and clone) all data for a test. +Datas.getParams = function () { + return (this.used = $.extend(true, {}, this.params)); +}; +return Datas; }); odoo.define('account.reconciliation_tests', function (require) { @@ -992,5 +993,229 @@ QUnit.module('account', { clientAction.destroy(); }); + + + QUnit.test('Reconciliation pager', function (assert) { + assert.expect(6); + + this.params.data['account.bank.statement.line'].records.push( + {id: 9, display_name: "add 9"}, + {id: 10, display_name: "add 10"}, + {id: 11, display_name: "add 11"}, + {id: 12, display_name: "add 12"}, + {id: 13, display_name: "add 13"}, + {id: 14, display_name: "add 14"}, + {id: 15, display_name: "add 15"}, + {id: 16, display_name: "add 16"} + ); + + var data_preprocess = this.params.data_preprocess; + data_preprocess.st_lines_ids.push(9, 10, 11, 12, 13, 14, 15, 16); + + this.params.data_widget.push( + { + 'st_line': { + 'currency_id': 3, + 'communication_partner_name': false, + 'open_balance_account_id': 287, + 'name': "add 9", + 'partner_name': "Camptocamp", + 'partner_id': 12, + 'has_no_partner': false, + 'journal_id': 84, + 'account_name': "Bank", + 'note': "", + 'amount': 650.0, + 'amount_str': "$ 650.00", + 'amount_currency_str': "", + 'date': "2017-01-01", + 'account_code': "101401", + 'ref': "", + 'id': 9, + 'statement_id': 2 + }, + 'reconciliation_proposition': [] + }, + { + 'st_line': { + 'currency_id': 3, + 'communication_partner_name': false, + 'open_balance_account_id': 287, + 'name': "add 10", + 'partner_name': "Camptocamp", + 'partner_id': 12, + 'has_no_partner': false, + 'journal_id': 84, + 'account_name': "Bank", + 'note': "", + 'amount': 650.0, + 'amount_str': "$ 650.00", + 'amount_currency_str': "", + 'date': "2017-01-01", + 'account_code': "101401", + 'ref': "", + 'id': 10, + 'statement_id': 2 + }, + 'reconciliation_proposition': [] + }, + { + 'st_line': { + 'currency_id': 3, + 'communication_partner_name': false, + 'open_balance_account_id': 287, + 'name': "add 11", + 'partner_name': "Camptocamp", + 'partner_id': 12, + 'has_no_partner': false, + 'journal_id': 84, + 'account_name': "Bank", + 'note': "", + 'amount': 650.0, + 'amount_str': "$ 650.00", + 'amount_currency_str': "", + 'date': "2017-01-01", + 'account_code': "101401", + 'ref': "", + 'id': 11, + 'statement_id': 2 + }, + 'reconciliation_proposition': [] + }, + { + 'st_line': { + 'currency_id': 3, + 'communication_partner_name': false, + 'open_balance_account_id': 287, + 'name': "add 12", + 'partner_name': "Camptocamp", + 'partner_id': 12, + 'has_no_partner': false, + 'journal_id': 84, + 'account_name': "Bank", + 'note': "", + 'amount': 650.0, + 'amount_str': "$ 650.00", + 'amount_currency_str': "", + 'date': "2017-01-01", + 'account_code': "101401", + 'ref': "", + 'id': 12, + 'statement_id': 2 + }, + 'reconciliation_proposition': [] + }, + { + 'st_line': { + 'currency_id': 3, + 'communication_partner_name': false, + 'open_balance_account_id': 287, + 'name': "add 13", + 'partner_name': "Camptocamp", + 'partner_id': 12, + 'has_no_partner': false, + 'journal_id': 84, + 'account_name': "Bank", + 'note': "", + 'amount': 650.0, + 'amount_str': "$ 650.00", + 'amount_currency_str': "", + 'date': "2017-01-01", + 'account_code': "101401", + 'ref': "", + 'id': 13, + 'statement_id': 2 + }, + 'reconciliation_proposition': [] + }, + { + 'st_line': { + 'currency_id': 3, + 'communication_partner_name': false, + 'open_balance_account_id': 287, + 'name': "add 14", + 'partner_name': "Camptocamp", + 'partner_id': 12, + 'has_no_partner': false, + 'journal_id': 84, + 'account_name': "Bank", + 'note': "", + 'amount': 650.0, + 'amount_str': "$ 650.00", + 'amount_currency_str': "", + 'date': "2017-01-01", + 'account_code': "101401", + 'ref': "", + 'id': 14, + 'statement_id': 2 + }, + 'reconciliation_proposition': [] + }, + { + 'st_line': { + 'currency_id': 3, + 'communication_partner_name': false, + 'open_balance_account_id': 287, + 'name': "add 15", + 'partner_name': "Camptocamp", + 'partner_id': 12, + 'has_no_partner': false, + 'journal_id': 84, + 'account_name': "Bank", + 'note': "", + 'amount': 650.0, + 'amount_str': "$ 650.00", + 'amount_currency_str': "", + 'date': "2017-01-01", + 'account_code': "101401", + 'ref': "", + 'id': 15, + 'statement_id': 2 + }, + 'reconciliation_proposition': [] + }, + { + 'st_line': { + 'currency_id': 3, + 'communication_partner_name': false, + 'open_balance_account_id': 287, + 'name': "add 16", + 'partner_name': "Camptocamp", + 'partner_id': 12, + 'has_no_partner': false, + 'journal_id': 84, + 'account_name': "Bank", + 'note': "", + 'amount': 650.0, + 'amount_str': "$ 650.00", + 'amount_currency_str': "", + 'date': "2017-01-01", + 'account_code': "101401", + 'ref': "", + 'id': 16, + 'statement_id': 2 + }, + 'reconciliation_proposition': [] + } + ); + + var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); + testUtils.addMockEnvironment(clientAction, { + 'data': this.params.data, + }); + + clientAction.appendTo($('#qunit-fixture')); + + assert.strictEqual(clientAction.$('.o_reconciliation_line').length, 10, "should display 10 lines"); + assert.strictEqual(clientAction.$('.js_load_more:visible').length, 1, "should display the load more button"); + clientAction.$('.o_reconcile:visible').click(); + assert.strictEqual(clientAction.$('.o_reconcile:visible').length, 0, "should remove the reconciled line"); + assert.strictEqual(clientAction.$('.o_reconciliation_line').length, 10, "should load one line to complete the 10"); + clientAction.$('.js_load_more').click(); + assert.strictEqual(clientAction.$('.o_reconciliation_line').length, 11, "should load the last record"); + assert.strictEqual(clientAction.$('.js_load_more:visible').length, 0, "should hide the load more button"); + + clientAction.destroy(); + }); }); });