[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
This commit is contained in:
Lucas Perais (lpe)
2018-07-18 09:31:26 +02:00
parent 44b77d3702
commit 92fed4a51e
3 changed files with 218 additions and 14 deletions
@@ -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
@@ -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);
});
@@ -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();
});
});
});