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');