[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:
@@ -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(' ', ' ');
|
||||
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user