From 92fed4a51e31dd467b23d759747e5a5f608989fe Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Wed, 11 Jul 2018 11:48:11 +0200 Subject: [PATCH 1/3] [FIX] account: reconciliation widget: partial reconcile 2 inv 1 pay Have two invoices Make a bank statement with one line, which amount is: Amount[Bank] < Amount[Inv1] + Amount[Inv2] AND Amount[Bank] > Amount[Inv1] Click on on the reconcile button on the form view of the bank statement On the reconciliation widget: Click on the first invoice to fully reconcile it with the payment Click on the second to partially reconcile it with the payment Before this commit, there was no way to let the second invoice open and partially reconciled. The only possibility was to make a write-off V10.0 allowed to to do such a flow After this commit, this use case is dealt with again OPW 1864689 closes #25721 --- .../js/reconciliation/reconciliation_model.js | 10 +- .../reconciliation/reconciliation_renderer.js | 25 ++- .../static/tests/reconciliation_tests.js | 197 ++++++++++++++++++ 3 files changed, 218 insertions(+), 14 deletions(-) diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js index 14b09e4485b..dc3451ee110 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_model.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js @@ -517,7 +517,6 @@ var StatementModel = BasicModel.extend({ }, /** * Force the partial reconciliation to display the reconciliate button. - * This method should only be called when there is onely one proposition. * * @param {string} handle * @returns {Deferred} @@ -527,14 +526,17 @@ var StatementModel = BasicModel.extend({ // Retrieve the toggle proposition var selected; - _.each(line.reconciliation_proposition, function (prop) { + var targetLineAmount = line.st_line.amount; + line.reconciliation_proposition.every(function (prop) { if (!prop.invalid) { - if (((line.balance.amount < 0 || !line.partial_reconcile) && prop.amount > 0 && line.st_line.amount > 0 && line.st_line.amount < prop.amount) || - ((line.balance.amount > 0 || !line.partial_reconcile) && prop.amount < 0 && line.st_line.amount < 0 && line.st_line.amount > prop.amount)) { + if (((line.balance.amount < 0 || !line.partial_reconcile) && prop.amount > 0 && targetLineAmount > 0 && targetLineAmount < prop.amount) || + ((line.balance.amount > 0 || !line.partial_reconcile) && prop.amount < 0 && targetLineAmount < 0 && targetLineAmount > prop.amount)) { selected = prop; return false; } + targetLineAmount -= prop.amount; } + return true; }); // If no toggled proposition found, reject it diff --git a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js index df34443435d..66c471dfbd3 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js @@ -359,18 +359,21 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { // loop state propositions var props = []; - var nb_debit_props = 0; - var nb_credit_props = 0; + var partialDebitProps = 0; + var partialCreditProps = 0; _.each(state.reconciliation_proposition, function (prop) { if (prop.display) { props.push(prop); - if (prop.amount < 0) - nb_debit_props += 1; - else if (prop.amount > 0) - nb_credit_props += 1; + if (prop.amount > 0 && prop.amount > state.st_line.amount) { + partialDebitProps++; + } else if (prop.amount < 0 && prop.amount < state.st_line.amount) { + partialCreditProps++; + } + } }); + var targetLineAmount = state.st_line.amount; _.each(props, function (line) { var $line = $(qweb.render("reconciliation.line.mv_line", {'line': line, 'state': state})); if (!isNaN(line.id)) { @@ -379,10 +382,10 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { .attr("data-content", qweb.render('reconciliation.line.mv_line.details', {'line': line})); } if (line.already_paid === false && - ((state.balance.amount_currency < 0 || line.partial_reconcile) && nb_credit_props == 1 - && line.amount > 0 && state.st_line.amount > 0 && state.st_line.amount < line.amount) || - ((state.balance.amount_currency > 0 || line.partial_reconcile) && nb_debit_props == 1 - && line.amount < 0 && state.st_line.amount < 0 && state.st_line.amount > line.amount)) { + ((state.balance.amount_currency < 0 || line.partial_reconcile) + && line.amount > 0 && state.st_line.amount > 0 && targetLineAmount < line.amount && partialDebitProps <= 1) || + ((state.balance.amount_currency > 0 || line.partial_reconcile) + && line.amount < 0 && state.st_line.amount < 0 && targetLineAmount > line.amount && partialCreditProps <= 1)) { var $cell = $line.find(line.amount > 0 ? '.cell_right' : '.cell_left'); var text; if (line.partial_reconcile) { @@ -396,6 +399,8 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { .prependTo($cell) .attr("data-content", text); } + targetLineAmount -= line.amount; + $props.append($line); }); diff --git a/addons/account/static/tests/reconciliation_tests.js b/addons/account/static/tests/reconciliation_tests.js index b2247c08651..e5ae62811fe 100644 --- a/addons/account/static/tests/reconciliation_tests.js +++ b/addons/account/static/tests/reconciliation_tests.js @@ -1,6 +1,14 @@ odoo.define('account.reconciliation_tests.data', function () { "use strict"; +/* + * Debug tip: + * To be able to "see" the test in the browser: + * var $body = $('body'); + * $body.addClass('debug'); + * clientAction.appendTo($body); + */ + var Datas = {}; var db = { @@ -1490,5 +1498,194 @@ QUnit.module('account', { clientAction.destroy(); }); + + QUnit.test('Reconciliation: Payment < inv1 + inv2(partial)', function (assert) { + assert.expect(4); + + /* + * One payment: $1175 + * Two Invoices + * The first invoice will be fully reconciled $650 + * The second invoice will be partially paid with the rest of the payment $999 + */ + + // modify the second line that is already in db to put it at $999 + var indexModif = _.findIndex(this.params.mv_lines['[5,"",0,6]'], function (line) {return line.id === 112}); + this.params.mv_lines['[5,"",0,6]'][indexModif] = + {account_type: "receivable", amount_currency_str: "", currency_id: false, date_maturity: "2017-02-07", date: "2017-01-08", + total_amount_str: "$ 999.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: "$ 999.00", debit: 999.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_reconciliations') { + assert.deepEqual(args.args, + [ + [5], // Id of the bank statement line + + [{counterpart_aml_dicts: + [{name:"INV/2017/0002", + debit: 0, + credit: 650, + counterpart_aml_id: 109}, + + {name: "INV/2017/0003", + debit: 0, + credit: 525, + counterpart_aml_id: 112}], + + payment_aml_ids: [], + partner_id: 8, + new_aml_dicts: []}] + ], "should call process_reconciliations with partial reconcile values"); + } + 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 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"); + + // 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"); + + $reconciliationAlert.click(); + + var $buttonReconcile = widget.$('button.o_reconcile:not(hidden)'); + + assert.equal($buttonReconcile.length, 1, + 'The reconcile button must be visible'); + + $buttonReconcile.click(); + + clientAction.destroy(); + }); + + QUnit.test('Reconciliation: payment and 2 partials', function (assert) { + assert.expect(6); + + /* + * One payment: $1175 + * Two Invoices as Inv1 = 1200; Inv2 = 1200: + * Payment < Inv1 AND Payment < Inv2 + * No partial reconcile is possible, as a write-off of 1225 is necessary + */ + + // modify the invoice line to have their amount > payment + var indexInv1 = _.findIndex(this.params.mv_lines['[5,"",0,6]'], function (line) {return line.id === 109}); + this.params.mv_lines['[5,"",0,6]'][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,6]'], function (line) {return line.id === 112}); + this.params.mv_lines['[5,"",0,6]'][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_reconciliations') { + assert.deepEqual(args.args, + [ + [5], // Id of the bank statement line + + [{counterpart_aml_dicts: + [{name:"INV/2017/0002", + debit: 0, + credit: 1200, + counterpart_aml_id: 109}, + + {name: "INV/2017/0003", + debit: 0, + credit: 1200, + counterpart_aml_id: 112}], + + payment_aml_ids: [], + partner_id: 8, + new_aml_dicts: [ + {account_id: 282, + credit: 0, + debit: 1225, + name: 'SAJ/2014/002 and SAJ/2014/003', + } + ]}] + ], "should call process_reconciliations with new aml dict reconcile values"); + } + 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 .line_info_button').length, + "should display the partial reconciliation alert"); + + // 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"); + + var writeOffCreate = widget.$('div.create'); + + assert.equal(writeOffCreate.length, 1, + 'A write-off creation should be present'); + + assert.equal(writeOffCreate.find('input[name=amount]').val(), -1225, + 'The right amount should be proposed for the write-off'); + + writeOffCreate.find('.create_account_id input.ui-autocomplete-input').click(); + $('ul.ui-autocomplete li a:first').click(); // select first account to do the write off in + + var $buttonReconcile = widget.$('button.o_reconcile:not(hidden)'); + + assert.equal($buttonReconcile.length, 1, + 'The reconcile button must be visible'); + + $buttonReconcile.click(); + + clientAction.destroy(); + }); }); }); From 2b470288f2585072d11669b883882a14427d03ff Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Wed, 18 Jul 2018 12:50:53 +0200 Subject: [PATCH 2/3] [FIX] portal, *: missing mobile menu toggler * website The mobile menu toggler only appeared when the 'website' app was installed. This was a mistake which was made with portal split at https://github.com/odoo/odoo/commit/c3c42658c529967f26c39f51b39059bf47078b57 --- addons/portal/views/portal_templates.xml | 6 ++++++ addons/website/views/website_templates.xml | 12 ++---------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/addons/portal/views/portal_templates.xml b/addons/portal/views/portal_templates.xml index fef256890ae..0c3c9a319b5 100644 --- a/addons/portal/views/portal_templates.xml +++ b/addons/portal/views/portal_templates.xml @@ -27,6 +27,12 @@