From 7d6a9924e5bb8ff14defeaa07002c08920222d7b Mon Sep 17 00:00:00 2001 From: Cedric Snauwaert Date: Fri, 28 Sep 2018 09:55:22 +0000 Subject: [PATCH] [IMP] account: possibility to edit amount in reconciliation widget Before we could only make a partial payment that was equal to the value of the statement line if the payment was greater than the statement line. Now we can modify the value of any line (black line) used in the statement reconciliation widget. This allow to have the following: Statement line of 500 Payment 1 of 500 Payment 2 of 500 We can reconcile the statement line with 250 from payment 1 and 250 from payment 2. Task: 1870895 closes odoo/odoo#27805 --- .../reconciliation/reconciliation_action.js | 12 +- .../js/reconciliation/reconciliation_model.js | 107 ++++++----- .../reconciliation/reconciliation_renderer.js | 84 ++++----- .../js/reconciliation/tour_reconciliation.js | 16 +- .../src/scss/account_reconciliation.scss | 15 +- .../static/src/xml/account_reconciliation.xml | 44 ++--- .../static/tests/reconciliation_tests.js | 167 ++++++++++++++---- 7 files changed, 284 insertions(+), 161 deletions(-) diff --git a/addons/account/static/src/js/reconciliation/reconciliation_action.js b/addons/account/static/src/js/reconciliation/reconciliation_action.js index bdca26fa15f..77f8a99e5c1 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_action.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_action.js @@ -22,12 +22,12 @@ var StatementAction = AbstractAction.extend(ControlPanelMixin, { change_offset: '_onAction', change_partner: '_onAction', add_proposition: '_onAction', - search_balance_amount: '_onAction', remove_proposition: '_onAction', update_proposition: '_onAction', create_proposition: '_onAction', + getPartialAmount: '_onActionPartialAmount', quick_create_proposition: '_onAction', - toggle_partial_reconcile: '_onAction', + partial_reconcile: '_onAction', validate: '_onValidate', change_name: '_onChangeName', close_statement: '_onCloseStatement', @@ -233,6 +233,14 @@ var StatementAction = AbstractAction.extend(ControlPanelMixin, { }); }, + _onActionPartialAmount: function(event) { + var self = this; + var handle = event.target.handle; + var line = this.model.getLine(handle); + var amount = this.model.getPartialReconcileAmount(handle, event.data); + self._getWidget(handle).updatePartialAmount(event.data.data, amount); + }, + /** * call 'changeName' model method * diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js index c76768103a9..d14110f10a2 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_model.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js @@ -43,7 +43,7 @@ var _t = core._t; * mode: string ('inactive', 'match', 'create') * reconciliation_proposition: { * id: number|string - * partial_reconcile: boolean + * partial_amount: number * invalid: boolean - through the invalid line (without account, label...) * is_tax: boolean * account_code: string @@ -533,24 +533,21 @@ var StatementModel = BasicModel.extend({ } return $.when.apply($, defs); }, - searchBalanceAmount: function (handle) { + getPartialReconcileAmount: function(handle, data) { var line = this.getLine(handle); - var amount = line.balance.amount; - var amount_str = _.str.sprintf('%.2f', Math.abs(amount)); - amount_str = (amount > '0' ? '-' : '+') + amount_str; - if (line.balance.currency_id && line.balance.amount_currency) { - var amount_currency = line.balance.amount_currency; - var amount_currency_str = _.str.sprintf('%.2f', Math.abs(amount_currency)); - amount_str += '|' + (amount_currency > '0' ? '-' : '+') + amount_currency_str; + var prop = _.find(line.reconciliation_proposition, {'id': data.data}); + if (prop) { + var amount = prop.partial_amount || prop.amount; + // Check if we can get a partial amount that would directly set balance to zero + var partial = Math.abs(line.balance.amount + amount); + if (Math.abs(line.balance.amount) >= Math.abs(amount)) { + return Math.abs(amount); + } + if (partial <= Math.abs(prop.amount) && partial >= 0) { + return partial + } + return Math.abs(amount); } - if (amount_str === line.filter) { - line.filter = ''; - line.offset = 0; - return this.changeMode(handle, 'create'); - } - line.filter = amount_str; - line.offset = 0; - return this.changeMode(handle, 'match'); }, /** * Force the partial reconciliation to display the reconciliate button. @@ -558,37 +555,35 @@ var StatementModel = BasicModel.extend({ * @param {string} handle * @returns {Deferred} */ - togglePartialReconcile: function (handle) { + partialReconcile: function(handle, data) { var line = this.getLine(handle); - - // Retrieve the toggle proposition - var selected = _.filter(line.reconciliation_proposition, function(prop){return prop.display_triangle}); - - // If no toggled proposition found, reject it - if(selected.length != 1) - return $.Deferred().reject(); - - selected = selected[0]; - - // Inverse partial_reconcile value - selected.partial_reconcile = !selected.partial_reconcile; - if (!selected.partial_reconcile) { - return this._computeLine(line); - } - - // Compute the write_off - var format_options = { currency_id: line.st_line.currency_id }; - selected.write_off_amount = selected.amount + line.balance.amount; - selected.write_off_amount_str = field_utils.format.monetary(Math.abs(selected.write_off_amount), {}, format_options); - selected.write_off_amount_str = selected.write_off_amount_str.replace(' ', ' '); - - return this._computeLine(line).then(function () { - if (selected.partial_reconcile) { - line.balance.amount = 0; - line.balance.type = 1; - line.mode = 'inactive'; + var prop = _.find(line.reconciliation_proposition, {'id' : data.mvLineId}); + if (prop) { + var amount = data.amount; + try { + amount = field_utils.parse.float(data.amount); } - }); + catch (err) { + amount = NaN; + } + // Amount can't be greater than line.amount and can not be negative and must be a number + // the amount we receive will be a string, so take sign of previous line amount in consideration in order to put + // the amount in the correct left or right column + if (amount >= Math.abs(prop.amount) || amount <= 0 || isNaN(amount)) { + delete prop.partial_amount_str; + delete prop.partial_amount; + if (isNaN(amount) || amount < 0) { + this.do_warn(_.str.sprintf(_t('The amount %s is not a valid partial amount'), data.amount)); + } + return this._computeLine(line); + } + else { + var format_options = { currency_id: line.st_line.currency_id }; + prop.partial_amount = (prop.amount > 0 ? 1 : -1)*amount; + prop.partial_amount_str = field_utils.format.monetary(Math.abs(prop.partial_amount), {}, format_options); + } + } + return this._computeLine(line); }, /** * Change the value of the editable proposition line or create a new one. @@ -759,9 +754,6 @@ var StatementModel = BasicModel.extend({ } line.reconciliation_proposition.push(prop); - _.each(line.reconciliation_proposition, function (prop) { - prop.partial_reconcile = false; - }); }, /** * stop the editable proposition line and remove it if it's invalid then @@ -830,6 +822,9 @@ var StatementModel = BasicModel.extend({ } return; } + if (!prop.already_paid && parseInt(prop.id)) { + prop.is_move_line = true; + } reconciliation_proposition.push(prop); if (prop.tax_id && prop.__tax_to_recompute && prop.base_amount) { @@ -891,7 +886,7 @@ var StatementModel = BasicModel.extend({ _.each(reconciliation_proposition, function (prop) { if (!prop.invalid) { - total -= prop.amount; + total -= prop.partial_amount || prop.amount; if (isOtherCurrencyId) { amount_currency -= (prop.amount < 0 ? -1 : 1) * Math.abs(prop.amount_currency); } @@ -915,6 +910,7 @@ var StatementModel = BasicModel.extend({ }) : false, account_code: self.accounts[line.st_line.open_balance_account_id], }; + line.balance.show_balance = line.balance.amount_currency != 0; line.balance.type = line.balance.amount_currency ? (line.st_line.partner_id ? 0 : -1) : 1; }); }, @@ -1168,7 +1164,7 @@ var StatementModel = BasicModel.extend({ var line = this.getLine(handle); var excluded_ids = _.compact(_.flatten(_.map(this.lines, function (line) { return _.map(line.reconciliation_proposition, function (prop) { - return !prop.partial_reconcile && _.isNumber(prop.id) ? prop.id : null; + return _.isNumber(prop.id) ? prop.id : null; }); }))); var filter = line.filter || ""; @@ -1200,8 +1196,8 @@ var StatementModel = BasicModel.extend({ */ _formatToProcessReconciliation: function (line, prop) { var amount = -prop.amount; - if (prop.partial_reconcile === true) { - amount = -prop.write_off_amount; + if (prop.partial_amount) { + amount = -prop.partial_amount; } var result = { @@ -1459,6 +1455,9 @@ var ManualModel = StatementModel.extend({ _computeLine: function (line) { return this._super(line).then(function () { var props = _.reject(line.reconciliation_proposition, 'invalid'); + _.each(line.reconciliation_proposition, function(p) { + delete p.is_move_line; + }); line.balance.type = -1; if (!line.balance.amount_currency && props.length) { line.balance.type = 1; @@ -1564,7 +1563,7 @@ var ManualModel = StatementModel.extend({ var line = this.getLine(handle); var excluded_ids = _.compact(_.flatten(_.map(this.lines, function (line) { return _.map(line.reconciliation_proposition, function (prop) { - return !prop.partial_reconcile && _.isNumber(prop.id) ? prop.id : null; + return _.isNumber(prop.id) ? prop.id : null; }); }))); var filter = line.filter || ""; diff --git a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js index 7d572bc74b3..1f2eaea2432 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js @@ -254,14 +254,13 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { 'click .accounting_view caption .o_buttons button': '_onValidate', 'click .accounting_view thead td': '_onTogglePanel', 'click .accounting_view tfoot td:not(.cell_left,.cell_right)': '_onShowPanel', - 'click tfoot .cell_left, tfoot .cell_right': '_onSearchBalanceAmount', + 'click .cell': '_onEditAmount', 'input input.filter': '_onFilterChange', 'click .match .load-more a': '_onLoadMore', 'click .match .mv_line td': '_onSelectMoveLine', 'click .accounting_view tbody .mv_line td': '_onSelectProposition', 'click .o_reconcile_models button': '_onQuickCreateProposition', 'click .create .add_line': '_onCreateProposition', - 'click .accounting_view .line_info_button.fa-exclamation-triangle': '_onTogglePartialReconcile', 'click .reconcile_model_create': '_onCreateReconcileModel', 'click .reconcile_model_edit': '_onEditReconcileModel', 'keyup input': '_onInputKeyup', @@ -284,7 +283,6 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { this.model = model; this._initialState = state; }, - /** * @override */ @@ -365,41 +363,15 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { // Search propositions that could be a partial credit/debit. var props = []; - var partialDebitProp; - var partialCreditProp; var balance = state.balance.amount_currency; _.each(state.reconciliation_proposition, function (prop) { if (prop.display) { props.push(prop); - - /* - Examples: - statement line | 100 | | - move line 1 | | 200 | <- can be a partial of 100 - balance: -100 - - statement line | 500 | | - move line 1 | | 300 | <- is not a eligible to be a partial due to the second line. - move line 2 | | 300 | <- can be a partial of 200 - balance: -100 - - statement line | 500 | | - move line 1 | | 700 | <- must not be a partial (debit = 800 > 700 = credit). - move line 2 | 300 | | - balance: 100 - */ - if(!prop.display_new && balance < 0 && prop.amount > 0 && balance + prop.amount > 0) - partialDebitProp = prop; - else if(!prop.display_new && balance > 0 && prop.amount < 0 && balance + prop.amount < 0) - partialCreditProp = prop; } }); _.each(props, function (line) { - line.display_triangle = (line.already_paid === false && - ((state.balance.amount_currency < 0 || line.partial_reconcile) && partialDebitProp && partialDebitProp === line) || - ((state.balance.amount_currency > 0 || line.partial_reconcile) && partialCreditProp && partialCreditProp === line)); - var $line = $(qweb.render("reconciliation.line.mv_line", {'line': line, 'state': state})); + var $line = $(qweb.render("reconciliation.line.mv_line", {'line': line, 'state': state, 'proposition': true})); if (!isNaN(line.id)) { $('') .appendTo($line.find('.cell_info_popover')) @@ -477,6 +449,15 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { this.$('.create .add_line').toggle(!!state.balance.amount_currency); }, + updatePartialAmount: function(line_id, amount) { + var $line = $('.mv_line[data-line-id='+line_id+']'); + $line.find('.edit_amount').addClass('d-none'); + $line.find('.edit_amount_input').removeClass('d-none'); + $line.find('.edit_amount_input').focus(); + $line.find('.edit_amount_input').val(amount.toFixed(2)); + $line.find('.line_amount').addClass('d-none'); + }, + //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- @@ -625,6 +606,21 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { target: 'current' }); }, + _editAmount: function (event) { + event.stopPropagation(); + var $line = $(event.target); + var moveLineId = $line.closest('.mv_line').data('line-id'); + this.trigger_up('partial_reconcile', {'data': {mvLineId: moveLineId, 'amount': $line.val()}}); + }, + _onEditAmount: function (event) { + event.preventDefault(); + event.stopPropagation(); + // Don't call when clicking inside the input field + if (! $(event.target).hasClass('edit_amount_input')){ + var $line = $(event.target); + this.trigger_up('getPartialAmount', {'data': $line.closest('.mv_line').data('line-id')}); + } + }, /** * @private * @param {MouseEvent} event @@ -664,12 +660,6 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { var mode = this.$el.data('mode') === 'inactive' ? 'match' : 'inactive'; this.trigger_up('change_mode', {'data': mode}); }, - /** - * @private - */ - _onSearchBalanceAmount: function () { - this.trigger_up('search_balance_amount'); - }, /** * @private */ @@ -694,12 +684,24 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { return; } if(event.keyCode === 13) { + if ($(event.target).hasClass('edit_amount_input')) { + this.$(event.target).blur(); + return; + } var created_lines = _.findWhere(this.model.lines, {mode: 'create'}); if (created_lines && created_lines.balance.amount) { this._onCreateProposition(); } return; } + if ($(event.target).hasClass('edit_amount_input')) { + if (event.type === 'keyup') { + return; + } + else { + return this._editAmount(event); + } + } var self = this; for (var fieldName in this.fields) { @@ -772,16 +774,6 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { */ _onValidate: function () { this.trigger_up('validate'); - }, - /** - * @private - * @param {MouseEvent} event - */ - _onTogglePartialReconcile: function (e) { - e.stopPropagation(); - var popover = $(e.target).data('bs.popover'); - popover && popover.dispose(); - this.trigger_up('toggle_partial_reconcile'); } }); diff --git a/addons/account/static/src/js/reconciliation/tour_reconciliation.js b/addons/account/static/src/js/reconciliation/tour_reconciliation.js index c672a24debb..f5eb679af51 100644 --- a/addons/account/static/src/js/reconciliation/tour_reconciliation.js +++ b/addons/account/static/src/js/reconciliation/tour_reconciliation.js @@ -27,7 +27,21 @@ Tour.register('bank_statement_reconciliation', { }, { content: "click on partial reconcile", - trigger: '.o_reconciliation_line:last .accounting_view .do_partial_reconcile_true' + trigger: '.o_reconciliation_line:last .accounting_view .edit_amount', + }, + { + content: "Edit amount", + trigger: '.o_reconciliation_line:last .accounting_view .edit_amount_input:not(.d-none)', + run: 'text 2000' + }, + { + content: "Press enter to validate amount", + trigger: '.o_reconciliation_line:last .accounting_view .edit_amount_input:not(.d-none)', + run: 'keydown 13' + }, + { + content: "Check that amount has changed", + trigger: '.o_reconciliation_line:last .accounting_view .line_amount:contains("2,000.00")' }, { content: "reconcile the line", diff --git a/addons/account/static/src/scss/account_reconciliation.scss b/addons/account/static/src/scss/account_reconciliation.scss index 6c21d14e8f2..0eda5d43fc0 100644 --- a/addons/account/static/src/scss/account_reconciliation.scss +++ b/addons/account/static/src/scss/account_reconciliation.scss @@ -58,14 +58,17 @@ border-right: 1px solid #333; padding-right: 5px; } - .do_partial_reconcile_true { - color: $o-community-color; // FIXME probably supposed to be primary to be auto-adapted in enterprise ? - padding-right: 5px; + .edit_amount { + margin-left: 20px; + color: #bbb; } - .do_partial_reconcile_false { - color: #f0eeee; - padding-right: 5px; + .cell:hover .edit_amount { + color: #00A09D; } + .strike_amount { + text-decoration: line-through; + } + } .o_multi_currency { diff --git a/addons/account/static/src/xml/account_reconciliation.xml b/addons/account/static/src/xml/account_reconciliation.xml index ec9e668ac4f..7538f40df4c 100644 --- a/addons/account/static/src/xml/account_reconciliation.xml +++ b/addons/account/static/src/xml/account_reconciliation.xml @@ -130,7 +130,7 @@ - + @@ -220,17 +220,27 @@ - - - - + + + + + + + +
+
+
+ + + - - - -
+ + + + +
@@ -256,20 +266,12 @@ - - - + - - - + diff --git a/addons/account/static/tests/reconciliation_tests.js b/addons/account/static/tests/reconciliation_tests.js index f2774ff6b30..f0028d664e9 100644 --- a/addons/account/static/tests/reconciliation_tests.js +++ b/addons/account/static/tests/reconciliation_tests.js @@ -423,11 +423,11 @@ var mv_lines = { '[6,"",0,5]': [ {'account_type': "liquidity", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-01-23", 'date': "2017-01-23", 'total_amount_str': "$ 376.00", 'partner_id': 7, 'account_name': "Bank", 'name': "BNK1/2017/0002: SUPP.OUT/2017/0002", 'partner_name': "ASUSTeK", 'total_amount_currency_str': "", 'id': 392, 'credit': 376.0, 'journal_id': "Bank", 'amount_str': "$ 376.00", 'debit': 0.0, 'account_code': "101401", 'ref': "BILL/2017/0003", 'already_paid': true}, {'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}, + {'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_code': "111100", 'ref': "", 'already_paid': false}, {'account_type': "liquidity", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-01-23", 'date': "2017-01-23", 'total_amount_str': "$ 525.50", 'partner_id': 8, 'account_name': "Bank", 'name': "BNK1/2017/0004: CUST.IN/2017/0002", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 396, 'credit': 0.0, 'journal_id': "Bank", 'amount_str': "$ 525.50", 'debit': 525.5, 'account_code': "101401", 'ref': "INV/2017/0003", 'already_paid': true}, {'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-22", 'date': "2017-01-23", 'total_amount_str': "$ 525.00", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0004", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 399, '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-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_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_code': "111100", 'ref': "", 'already_paid': false}, {'account_type': "payable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-28", 'date': "2017-01-15", 'total_amount_str': "$ 5,749.99", 'partner_id': 7, 'account_name': "Account Payable", 'name': "BILL/2017/0002", 'partner_name': "ASUSTeK", 'total_amount_currency_str': "", 'id': 117, 'credit': 5749.99, 'journal_id': [2, "Vendor Bills"], 'amount_str': "$ 5,749.99", 'debit': 0.0, 'account_code': "111100", 'ref': "", 'already_paid': false} ], '[6,"",5,5]': [ @@ -689,12 +689,12 @@ QUnit.module('account', { clientAction.widgets[1].$('.accounting_view thead td:first').trigger('click'); assert.containsN(clientAction.widgets[1], '.mv_line', 5, "should display 5 account move lines"); - assert.strictEqual(clientAction.widgets[1].$('.mv_line .cell_right:contains(".")').length, 4, "should display only the credit account move lines (hide the debit)"); + assert.strictEqual(clientAction.widgets[1].$('.mv_line .cell_right:contains(".")').length, 3, "should display only the credit account move lines (hide the debit)"); assert.containsN(clientAction.widgets[1], '.mv_line.already_reconciled', 3, "should display 3 already reconciled account move lines"); assert.strictEqual(clientAction.widgets[1].$('.mv_line').text().replace(/[\n\r\s]+/g, ' '), - " 101401 2017-01-23 ASUSTeK: BNK1/2017/0002: SUPP.OUT/2017/0002 : BILL/2017/0003 $ 376.00 101401 2017-01-23 Agrolait: BNK1/2017/0003: CUST.IN/2017/0001 $ 100.00 101401 2017-01-23 Agrolait: BNK1/2017/0004: CUST.IN/2017/0002 : INV/2017/0003 $ 525.50 101200 2017-02-07 Agrolait: INV/2017/0002 $ 650.00 101200 2017-02-22 Agrolait: INV/2017/0004 $ 525.00 ", + " 101401 2017-01-23 ASUSTeK: BNK1/2017/0002: SUPP.OUT/2017/0002 : BILL/2017/0003 $ 376.00 101401 2017-01-23 Agrolait: BNK1/2017/0003: CUST.IN/2017/0001 $ 100.00 111100 2017-02-28 Camptocamp: BILL/2017/0001 $ 10,000.00 101401 2017-01-23 Agrolait: BNK1/2017/0004: CUST.IN/2017/0002 : INV/2017/0003 $ 525.50 101200 2017-02-07 Agrolait: INV/2017/0002 $ 650.00 ", "should display 4 account move lines who contains the account_code, due_date, label and the credit"); - assert.strictEqual(clientAction.widgets[1].$('.mv_line .cell_left:contains(".")').length, 1, "should display only 1 debit account move lines"); + assert.strictEqual(clientAction.widgets[1].$('.mv_line .cell_left:contains(".")').length, 2, "should display only 2 debit account move lines"); // load more assert.ok(clientAction.widgets[1].$('.match div.load-more a:visible').length, "should display the 'load more' button"); @@ -846,7 +846,7 @@ QUnit.module('account', { }); QUnit.test('Reconciliation partial', function (assert) { - assert.expect(10); + assert.expect(9); var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); testUtils.mock.addMockEnvironment(clientAction, { @@ -855,12 +855,19 @@ QUnit.module('account', { console.log(args.method); if (args.method === 'process_bank_statement_line') { var lines = args.args['1']; + console.log(args.arsg); assert.deepEqual(args.args, [ [6], [{ partner_id: lines.length == 1 ? lines[0].partner_id : false, - counterpart_aml_dicts:[], - payment_aml_ids: [392], + counterpart_aml_dicts:[{ + "analytic_tag_ids": [[6, null, []]], + "counterpart_aml_id": 114, + "credit": 0, + "debit": 32.58, + "name": "BILL/2017/0001" + }], + payment_aml_ids: [], new_aml_dicts: [], }] ], "should call process_bank_statement_line with partial reconcile values"); @@ -883,26 +890,29 @@ QUnit.module('account', { var widget = clientAction.widgets[0]; widget.$('.match .cell_account_code:first').trigger('click'); - assert.notOk( widget.$('.cell_right .line_info_button').length, "should not display the partial reconciliation alert"); + assert.equal( widget.$('.cell_right .edit_amount').length, 1, "should display the edition pencil"); widget = clientAction.widgets[1]; - 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.strictEqual(widget.$('.match .mv_line[data-line-id=114] .cell_account_code:first()').length, 1, 'Should have line') + widget.$('.match .mv_line[data-line-id=114] .cell_account_code').trigger('click'); + + assert.equal( widget.$('.accounting_view tbody .cell_left .edit_amount').length, 1, "should display the edition pencil"); // The partner has been set automatically, remove it. widget.$('.o_input_dropdown input').trigger('click'); widget.$('.o_input_dropdown input').val('').trigger('keyup').trigger('blur'); - assert.containsNone(widget, 'button.btn-primary:visible', "should display the reconcile model buttons"); - assert.containsOnce(widget, '.text-danger:visible', "should display counterpart alert"); - widget.$('.accounting_view .cell_left .line_info_button').trigger('click'); - assert.containsOnce(widget, '.accounting_view .cell_left .line_info_button', "should display a partial reconciliation alert"); - assert.doesNotHaveClass(widget.$('.accounting_view .cell_left .line_info_button'), 'do_partial_reconcile_true', "should display the partial reconciliation information"); - assert.containsOnce(widget, 'button.btn-primary:visible', "should display the reconcile button"); - assert.strictEqual( widget.$el.data('mode'), "inactive", "should be inactive mode"); + assert.strictEqual(widget.$('button.btn-primary:visible').length, 0, "should display the reconcile model buttons"); + assert.strictEqual(widget.$('.text-danger:visible').length, 1, "should display counterpart alert"); + widget.$('.accounting_view .cell_left .edit_amount').trigger('click'); + assert.strictEqual(widget.$('.accounting_view .cell_left .edit_amount_input:not(.d-none)').length, 1, "should display the input field to edit amount"); + // Edit amount + widget.$('.accounting_view .cell_left .edit_amount_input:not(.d-none)').val('32.58').trigger('blur'); + assert.strictEqual(widget.$('.accounting_view .cell_left .line_amount').text().replace(/[\n\r\s]+/g, ' '), " $ 10000.00 $ 32.58 ", "should display previous amount and new amount") + + assert.strictEqual(widget.$('button.btn-primary:visible').length, 1, "should display the reconcile button"); widget.$('button.btn-primary:visible').trigger('click'); clientAction.destroy(); @@ -1320,7 +1330,7 @@ QUnit.module('account', { }); QUnit.test('Reconciliation: Payment < inv1 + inv2(partial)', function (assert) { - assert.expect(4); + assert.expect(3); /* * One payment: $1175 @@ -1383,17 +1393,15 @@ QUnit.module('account', { // Add first invoice to reconcile fully widget.$('.match .cell_account_code:first').trigger('click'); - assert.notOk( widget.$('.cell_right .line_info_button').length, - "should not display the partial reconciliation alert"); + assert.ok( widget.$('.cell_right .edit_amount').length, + "should display the pencil to edit amount"); // Add second invoice to reconcile partially widget.$('.match .cell_account_code:first').trigger('click'); - var $reconciliationAlert = widget.$('.cell_right .line_info_button'); - assert.ok($reconciliationAlert.length, - "should display the partial reconciliation alert"); - - testUtils.dom.click($reconciliationAlert); + // Edit amount on last invoice + testUtils.dom.click(widget.$('.edit_amount:last()')); + widget.$('.edit_amount_input:last()').val('525').trigger('blur'); var $buttonReconcile = widget.$('button.o_reconcile:not(hidden)'); @@ -1484,13 +1492,13 @@ QUnit.module('account', { // Add first invoice // There should be the opportunity to reconcile partially widget.$('.match .cell_account_code:first').trigger('click'); - assert.ok(widget.$('.cell_right .line_info_button').length, - "should display the partial reconciliation alert"); + assert.ok(widget.$('.cell_right .edit_amount').length, + "should display the pencil to edit amount"); // Add second invoice widget.$('.match .cell_account_code:first').trigger('click'); - assert.notOk(widget.$('.cell_right .line_info_button').length, - "should not display the partial reconciliation alert"); + assert.ok(widget.$('.cell_right .edit_amount').length, + "should display the pencil to edit amount"); var writeOffCreate = widget.$('div.create'); @@ -1513,6 +1521,103 @@ QUnit.module('account', { clientAction.destroy(); }); + QUnit.test('Reconciliation: partial payment of 2 invoices with one payment', function (assert) { + assert.expect(4); + + /* + * One payment: $1175 + * Two Invoices as Inv1 = 1200; Inv2 = 1200: + * Payment < Inv1 AND Payment < Inv2 + * Assign 500 to inv1 and 675 to inv2 + */ + + // modify the invoice line to have their amount > payment + var indexInv1 = _.findIndex(this.params.mv_lines['[5,"",0,5]'], function (line) {return line.id === 109}); + this.params.mv_lines['[5,"",0,5]'][indexInv1] = + {account_type: "receivable", amount_currency_str: "", currency_id: false, date_maturity: "2017-02-07", date: "2017-01-08", + total_amount_str: "$ 1200.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: "$ 1200.00", debit: 1200.0, + account_code: "101200", ref: "", already_paid: false}; + + var indexInv2 = _.findIndex(this.params.mv_lines['[5,"",0,5]'], function (line) {return line.id === 112}); + this.params.mv_lines['[5,"",0,5]'][indexInv2] = + {account_type: "receivable", amount_currency_str: "", currency_id: false, date_maturity: "2017-02-07", date: "2017-01-08", + total_amount_str: "$ 1200.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: "$ 1200.00", debit: 1200.0, account_code: "101200", ref: "", already_paid: false}; + + var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); + testUtils.addMockEnvironment(clientAction, { + data: this.params.data, + mockRPC: function (route, args) { + if (args.method === 'process_bank_statement_line') { + assert.deepEqual(args.args, + [ + [5], // Id of the bank statement line + + [{counterpart_aml_dicts: + [{name:"INV/2017/0002", + debit: 0, + credit: 500, + analytic_tag_ids: [[6, null, []]], + counterpart_aml_id: 109}, + + {name: "INV/2017/0003", + debit: 0, + credit: 675, + analytic_tag_ids: [[6, null, []]], + counterpart_aml_id: 112}], + + payment_aml_ids: [], + partner_id: 8, + new_aml_dicts: [] + }] + ], "should call process_bank_statement_line with correct counterpart_aml_dicts"); + } + return this._super(route, args); + }, + session: { + currencies: { + 3: { + digits: [69, 2], + position: "before", + symbol: "$" + } + } + }, + }); + clientAction.appendTo($('#qunit-fixture')); + + // The first reconciliation "line" is where it happens + var widget = clientAction.widgets[0]; + + // Add first invoice + // There should be the opportunity to reconcile partially + widget.$('.match .cell_account_code:first').trigger('click'); + assert.ok(widget.$('.cell_right .edit_amount').length, + "should display the pencil to edit amount"); + + // Add second invoice + widget.$('.match .cell_account_code:first').trigger('click'); + assert.ok(widget.$('.cell_right .edit_amount').length, + "should display the pencil to edit amount"); + + // Edit invoice first amount + widget.$('.edit_amount:first()').trigger('click'); + widget.$('.edit_amount_input:first()').val('500').trigger('blur'); + // Edit invoice second amount + var $buttonReconcile = widget.$('button.o_reconcile:not(hidden)'); + widget.$('.edit_amount:last()').trigger('click'); + widget.$('.edit_amount_input:last()').val('675').trigger('blur'); + + assert.equal($buttonReconcile.length, 1, + 'The reconcile button must be visible'); + + $buttonReconcile.click(); + + clientAction.destroy(); + }); + QUnit.test('Manual Reconciliation: remove a prop to attain balance and reconcile', function (assert) { assert.expect(5); @@ -1559,7 +1664,7 @@ QUnit.module('account', { // Add first prop widget.$('.match .cell_account_code:first').trigger('click'); - assert.equal( widget.$('.cell_right .line_info_button').length, 1, "should display the partial reconciliation alert"); + assert.equal( widget.$('.cell_right .edit_amount').length, 0, "should not display the pencil to edit amount"); // Add second prop widget.$('.match .cell_account_code:first').trigger('click');