[IMP] account: allow partial reconciliation with write-off

Suppose you have an invoice of 3000. You want to pay with paypal
    3 x 1000 but having of fee of 10 in each transaction. So, you attempt
    to reconcile 3000 with 3 x (990 + 10) that is not possible with the
    reconciliation widget.

Was PR #17489. Was task: https://www.odoo.com/web#id=23384&view_type=form&model=project.task&action=333&active_id=967&menu_id=4720
This commit is contained in:
Laurent Smet
2018-02-07 16:48:53 +01:00
committed by qdp-odoo
parent 197a5f3887
commit b10932d8a5
3 changed files with 56 additions and 28 deletions
@@ -526,17 +526,37 @@ var StatementModel = BasicModel.extend({
*/
togglePartialReconcile: function (handle) {
var line = this.getLine(handle);
var props = _.filter(line.reconciliation_proposition, {'invalid': false});
var prop = props[0];
if (props.length !== 1 || Math.abs(line.st_line.amount) >= Math.abs(prop.amount)) {
// Retrieve the toggle proposition
var selected;
_.each(line.reconciliation_proposition, 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)) {
selected = prop;
return false;
}
}
});
// If no toggled proposition found, reject it
if (selected == null)
return $.Deferred().reject();
}
prop.partial_reconcile = !prop.partial_reconcile;
if (!prop.partial_reconcile) {
// 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('&nbsp;', ' ');
return this._computeLine(line).then(function () {
if (prop.partial_reconcile) {
if (selected.partial_reconcile) {
line.balance.amount = 0;
line.balance.type = 1;
line.mode = 'inactive';
@@ -646,6 +666,7 @@ var StatementModel = BasicModel.extend({
return isNaN(prop.id);
}), self._formatToProcessReconciliation.bind(self, line)),
};
// If the lines are not fully balanced, create an unreconciled amount.
// line.st_line.currency_id is never false here because its equivalent to
// statement_line.currency_id or statement_line.journal_id.currency_id or statement_line.journal_id.company_id.currency_id (Python-side).
@@ -1078,8 +1099,9 @@ var StatementModel = BasicModel.extend({
// Do not forward port in master. @CSN will change this
var amount = prop.computed_with_tax && -prop.base_amount || -prop.amount;
if (prop.partial_reconcile === true) {
amount = -line.st_line.amount;
amount = -prop.write_off_amount;
}
var result = {
name : prop.label,
debit : amount > 0 ? amount : 0,
@@ -353,7 +353,21 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
// reconciliation_proposition
var $props = this.$('.accounting_view tbody').empty();
var props = _.filter(state.reconciliation_proposition, {'display': true});
// loop state propositions
var props = [];
var nb_debit_props = 0;
var nb_credit_props = 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;
}
});
_.each(props, function (line) {
var $line = $(qweb.render("reconciliation.line.mv_line", {'line': line, 'state': state}));
if (!isNaN(line.id)) {
@@ -361,18 +375,16 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
.appendTo($line.find('.cell_info_popover'))
.attr("data-content", qweb.render('reconciliation.line.mv_line.details', {'line': line}));
}
if ((state.balance.amount_currency !== 0 || line.partial_reconcile) && props.length === 1 &&
line.already_paid === false &&
(
(state.st_line.amount > 0 && state.st_line.amount < props[0].amount) ||
(state.st_line.amount < 0 && state.st_line.amount > props[0].amount))
) {
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)) {
var $cell = $line.find(line.amount > 0 ? '.cell_right' : '.cell_left');
var text;
if (line.partial_reconcile) {
text = _t("Undo the partial reconciliation.");
$cell.text(state.st_line.amount_str);
$cell.text(line.write_off_amount_str);
} else {
text = _t("This move's amount is higher than the transaction's amount. Click to register a partial payment and keep the payment balance open.");
}
@@ -775,13 +775,7 @@ QUnit.module('account', {
partner_id: false,
counterpart_aml_dicts:[],
payment_aml_ids: [392],
new_aml_dicts: [
{
"credit": 343.42,
"debit": 0,
"name": "Bank fees : Open balance"
}
],
new_aml_dicts: [],
}]
], "should call process_reconciliations with partial reconcile values");
}
@@ -810,14 +804,14 @@ QUnit.module('account', {
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, 0, "should not display the partial reconciliation alert");
assert.equal( widget.$('.accounting_view tbody .cell_left .line_info_button').length, 1, "should display the partial reconciliation alert");
assert.ok( widget.$('button.btn-primary:not(hidden)').length, "should not display the reconcile button");
assert.ok( widget.$('.text-danger:not(hidden)').length, "should display counterpart alert");
widget.$('.accounting_view .cell_left .line_info_button').trigger('click');
assert.strictEqual(widget.$('.accounting_view .cell_left .line_info_button').length, 0, "should not display a partial reconciliation alert");
assert.notOk(widget.$('.accounting_view .cell_left .line_info_button').hasClass('do_partial_reconcile_false'), "should not display the partial reconciliation information");
assert.strictEqual(widget.$('.accounting_view .cell_left .line_info_button').length, 1, "should display a partial reconciliation alert");
assert.notOk(widget.$('.accounting_view .cell_left .line_info_button').hasClass('do_partial_reconcile_true'), "should display the partial reconciliation information");
assert.ok( widget.$('button.btn-default:not(hidden)').length, "should display the validate button");
assert.strictEqual( widget.$el.data('mode'), "match", "should be inactive mode");
assert.strictEqual( widget.$el.data('mode'), "inactive", "should be inactive mode");
widget.$('button.btn-default:not(hidden)').trigger('click');
clientAction.destroy();