[FIX] account: fix various bugs in reconciliation

* partial reconcile, display, compute, process_reconciliations
* more information in tooltip
* add domain to filter partner
This commit is contained in:
Christophe Matthieu
2017-04-11 19:44:38 +02:00
committed by Aaron Bohy
parent 2a40c78a8b
commit 97c6dc7792
5 changed files with 140 additions and 67 deletions
@@ -402,9 +402,22 @@ var StatementModel = BasicModel.extend({
*/
togglePartialReconcile: function (handle) {
var line = this.getLine(handle);
var prop = _.find(line.reconciliation_proposition, {'invalid': false});
var props = _.filter(line.reconciliation_proposition, {'invalid': false});
if (props.length !== 1) {
return $.Deferred().reject();
}
var prop = props[0];
prop.partial_reconcile = !prop.partial_reconcile;
return this._computeLine(line);
if (!prop.partial_reconcile) {
return this._computeLine(line);
}
return this._computeLine(line).then(function () {
if (prop.partial_reconcile) {
line.balance.amount = 0;
line.balance.type = 1;
line.mode = 'inactive';
}
});
},
/**
* Change the value of the editable proposition line or create a new one.
@@ -472,9 +485,15 @@ var StatementModel = BasicModel.extend({
ids.push(line.id);
values.push({
"partner_id": line.st_line.partner_id,
"counterpart_aml_dicts": _.map(_.filter(props, function (prop) {return !isNaN(prop.id) && !prop.already_paid;}), self._formatToProcessReconciliation),
"payment_aml_ids": _.map(_.filter(props, function (prop) {return !isNaN(prop.id) && prop.already_paid;}), self._formatToProcessReconciliation),
"new_aml_dicts": _.map(_.filter(props, function (prop) {return isNaN(prop.id);}), self._formatToProcessReconciliation),
"counterpart_aml_dicts": _.map(_.filter(props, function (prop) {
return !isNaN(prop.id) || prop.partial_reconcile;
}), self._formatToProcessReconciliation.bind(self, line)),
"payment_aml_ids": _.pluck(_.filter(props, function (prop) {
return !isNaN(prop.id) && !prop.partial_reconcile;
}), 'id'),
"new_aml_dicts": _.map(_.filter(props, function (prop) {
return isNaN(prop.id);
}), self._formatToProcessReconciliation.bind(self, line)),
});
line.reconciled = true;
self.valuenow++;
@@ -514,8 +533,8 @@ var StatementModel = BasicModel.extend({
}
line.reconciliation_proposition.push(prop);
_.each(line.reconciliation_proposition, function (line) {
line.partial_reconcile = false;
_.each(line.reconciliation_proposition, function (prop) {
prop.partial_reconcile = false;
});
},
/**
@@ -627,13 +646,6 @@ var StatementModel = BasicModel.extend({
'account_code': self.accounts[line.st_line.open_balance_account_id],
};
line.balance.type = line.balance.amount ? (line.balance.amount > 0 && line.st_line.partner_id ? 0 : -1) : 1;
if (line.balance.type === -1) {
var props = _.filter(line.reconciliation_proposition, {'invalid': false});
if (props.length === 1 && props[0].partial_reconcile) {
line.balance.type = 0;
}
}
});
},
/**
@@ -678,6 +690,7 @@ var StatementModel = BasicModel.extend({
prop.amount = prop.debit || -prop.credit;
prop.label = prop.name;
prop.account_id = self._formatNameGet(prop.account_id || line.account_id);
prop.is_partially_reconciled = prop.amount_str !== prop.total_amount_str;
});
}
},
@@ -791,7 +804,7 @@ var StatementModel = BasicModel.extend({
* @returns {Boolean}
*/
_isValid: function (prop) {
return prop.account_id && prop.amount && prop.label && !!prop.label.length;
return !isNaN(prop.id) || prop.account_id && prop.amount && prop.label && !!prop.label.length;
},
/**
* Fetch 'account.bank.statement.line' propositions.
@@ -804,9 +817,11 @@ var StatementModel = BasicModel.extend({
*/
_performMoveLine: function (handle) {
var line = this.getLine(handle);
var excluded_ids = _.flatten(_.map(this.lines, function (line) {
return _.filter(_.pluck(line.reconciliation_proposition, 'id'), _.isNumber);
}));
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;
});
})));
var filter = line.filter || "";
var offset = line.offset;
var limit = 6;
@@ -821,17 +836,25 @@ var StatementModel = BasicModel.extend({
* format the proposition to send information server side
*
* @private
* @param {object} line
* @param {object} proposition
* @returns {object}
*/
_formatToProcessReconciliation: function (prop) {
_formatToProcessReconciliation: function (line, prop) {
var amount = prop.partial_reconcile ? -line.st_line.amount : prop.amount;
var result = {
'name' : prop.label,
'debit' : prop.amount > 0 ? prop.amount : 0,
'credit' : prop.amount < 0 ? -prop.amount : 0,
'account_id' : prop.account_id.id,
'journal_id' : prop.journal_id && prop.journal_id.id,
name : prop.label,
debit : amount > 0 ? amount : 0,
credit : amount < 0 ? -amount : 0,
};
if (!isNaN(prop.id)) {
result.counterpart_aml_id = prop.id;
} else {
result.account_id = prop.account_id.id;
if (prop.journal_id) {
result.journal_id = prop.account_id.id;
}
}
if (!isNaN(prop.id)) result.counterpart_aml_id = prop.id;
if (prop.analytic_account_id) result.analytic_account_id = prop.analytic_account_id.id;
if (prop.tax_id) result.tax_ids = [[4, prop.tax_id.id, null]];
@@ -970,7 +993,7 @@ var ManualModel = StatementModel.extend({
});
} else {
var mv_line_ids = _.pluck(_.filter(props, function (prop) {return !isNaN(prop.id);}), 'id');
var new_mv_line_dicts = _.map(_.filter(props, function (prop) {return isNaN(prop.id);}), self._formatToProcessReconciliation);
var new_mv_line_dicts = _.map(_.filter(props, function (prop) {return isNaN(prop.id);}), self._formatToProcessReconciliation.bind(self, line));
process_reconciliations.push({
id: null,
type: null,
@@ -1127,9 +1150,11 @@ var ManualModel = StatementModel.extend({
*/
_performMoveLine: function (handle) {
var line = this.getLine(handle);
var excluded_ids = _.flatten(_.map(this.lines, function (line) {
return _.filter(_.pluck(line.reconciliation_proposition, 'id'), _.isNumber);
}));
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;
});
})));
var filter = line.filter || "";
var offset = line.offset;
var limit = 6;
@@ -194,7 +194,7 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
'click .accounting_view tbody .mv_line td': '_onSelectProposition',
'click .o_reconcile_models button': '_onQuickCreateProposition',
'click .create .add_line': '_onCreateProposition',
'click .accounting_view .cell_right .line_info_button': '_onTogglePartialReconcile',
'click .accounting_view .line_info_button.fa-exclamation-triangle': '_onTogglePartialReconcile',
'click .reconcile_model_create': '_onCreateReconcileModel',
'click .reconcile_model_edit': '_onEditReconcileModel',
},
@@ -290,12 +290,18 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
.attr("data-content", qweb.render('reconciliation.line.mv_line.details', {'line': line}));
}
if (state.balance.amount < 0 && props.length === 1) {
var text = !line.partial_reconcile ?
_t("This move's amount is higher than the transaction's amount. Click to register a partial payment and keep the payment balance open."):
_t("Undo the partial reconciliation.");
if ((state.balance.amount !== 0 || line.partial_reconcile) && props.length === 1) {
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);
} 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.");
}
$('<span class="do_partial_reconcile_'+(!line.partial_reconcile)+' line_info_button fa fa-exclamation-triangle"/>')
.prependTo($line.find('.cell_right'))
.prependTo($cell)
.attr("data-content", text);
}
$props.append($line);
@@ -353,7 +359,11 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
type: 'many2one',
name: 'partner_id',
value: [partnerID, partnerName]
}]);
}], {
partner_id: {
domain: [["parent_id", "=", false], "|", ["customer", "=", true], ["supplier", "=", true]]
}
});
return this.model.get(recordID);
},
@@ -176,6 +176,7 @@
vertical-align: top;
padding-right: 10px;
font-weight: bold;
white-space: nowrap;
}
}
@@ -112,7 +112,7 @@
</t>
<t t-name="reconciliation.line.balance">
<tr t-if="state.balance.amount">
<tr t-if="state.balance.amount &amp;&amp; !(state.reconciliation_proposition[0] || {}).partial_reconcile">
<td class="cell_action"><span class="toggle_create fa fa-play"></span></td>
<td class="cell_account_code"><t t-esc="state.balance.account_code"/></td>
<td class="cell_due_date"></td>
@@ -211,7 +211,8 @@
<tr t-if="line.partner_id"><td>Partner</td><td><t t-esc="line.partner_name"/></td></tr>
<tr><td>Date</td><td><t t-esc="line.date"/></td></tr>
<tr><td>Due Date</td><td><t t-esc="line.date_maturity === false ? line.date : line.date_maturity"/></td></tr>
<tr><td><t t-if="line.is_partially_reconciled">Residual</t><t t-if="! line.is_partially_reconciled">Amount</t></td><td>
<tr><td>Amount</td><td><t t-raw="line.total_amount_str"/><t t-if="line.total_amount_currency_str"> (<t t-esc="line.total_amount_currency_str"/>)</t></td></tr>
<tr t-if="line.is_partially_reconciled"><td>Residual</td><td>
<t t-raw="line.amount_str"/><t t-if="line.amount_currency_str"> (<t t-esc="line.amount_currency_str"/>)</t>
</td></tr>
<tr class="one_line_info" t-if='line.already_paid'>
@@ -7,14 +7,16 @@ var data = {
id: {string: "ID", type: 'integer'},
display_name: {string: "Displayed name", type: 'char'},
image: {string: "image", type: 'integer'},
customer: {string: "customer", type: 'boolean'},
supplier: {string: "supplier", type: 'boolean'},
},
records: [
{id: 1, display_name: "partner 1", image: 'AAA'},
{id: 2, display_name: "partner 2", image: 'BBB'},
{id: 3, display_name: "partner 3", image: 'CCC'},
{id: 4, display_name: "partner 4", image: 'DDD'},
{id: 8, display_name: "Agrolait", image: 'EEE'},
{id: 12, display_name: "Camptocamp", image: 'FFF'},
{id: 1, display_name: "partner 1", image: 'AAA', customer: true},
{id: 2, display_name: "partner 2", image: 'BBB', customer: true},
{id: 3, display_name: "partner 3", image: 'CCC', customer: true},
{id: 4, display_name: "partner 4", image: 'DDD', customer: true},
{id: 8, display_name: "Agrolait", image: 'EEE', customer: true},
{id: 12, display_name: "Camptocamp", image: 'FFF', supplier: true},
],
mark_as_reconciled: function () {
return $.when();
@@ -132,14 +134,15 @@ var data = {
return $.when(auto_reconciliation);
},
process_reconciliations: function (args) {
var datas = args[0];
for (var i in datas) {
var data = datas[i];
for (var key in move_lines_for_manual_reconciliation) {
move_lines_for_manual_reconciliation[key] = _.filter(move_lines_for_manual_reconciliation[key], function (mv_line) {
return data.mv_line_ids.indexOf(mv_line.id) === -1;
});
}
var datas = args[1];
var ids = _.flatten(_.pluck(_.pluck(datas, 'counterpart_aml_dicts'), 'counterpart_aml_id'));
ids = ids.concat(_.flatten(_.pluck(datas, 'payment_aml_ids')));
ids = _.compact(ids);
for (var key in move_lines_for_manual_reconciliation) {
move_lines_for_manual_reconciliation[key] = _.filter(move_lines_for_manual_reconciliation[key], function (mv_line) {
return ids.indexOf(mv_line.id) === -1;
});
}
return $.when();
},
@@ -554,11 +557,11 @@ QUnit.module('account', {
QUnit.test('Reconciliation basic data', function (assert) {
assert.expect(20);
assert.expect(13);
var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options);
testUtils.addMockEnvironment(clientAction, {
'data': this.params.data,
'data': this.params.data
});
clientAction.appendTo($('#qunit-fixture'));
@@ -591,24 +594,57 @@ QUnit.module('account', {
clientAction.widgets[3].$('.accounting_view thead td:first').trigger('click');
assert.strictEqual(clientAction.widgets[3].$el.data('mode'), 'create', "should switch to 'create' mode instead 'match' mode when 'match' mode is empty");
widget = clientAction.widgets[2];
assert.notOk( widget.$('.cell_right .line_info_button').length, "should not display the partial reconciliation alert");
assert.ok( widget.$('button.btn-primary:not(hidden)').length, "should display the reconcile button");
widget.$('.accounting_view thead td:first').trigger('click');
widget.$('.match td.cell_label:first').trigger('click');
assert.ok( widget.$('.text-danger:not(hidden)').length, "should display counterpart alert");
widget.$('.accounting_view tbody td.cell_label:first').trigger('click');
assert.ok( widget.$('.text-danger:not(hidden)').length, "should display counterpart alert");
assert.strictEqual(widget.$('.cell_right .line_info_button').length, 1, "should display a partial reconciliation alert");
widget.$('.cell_right .line_info_button').trigger('click');
assert.ok(widget.$('.cell_right .line_info_button').hasClass('do_partial_reconcile_false'), "should display the partial reconciliation information");
assert.ok( widget.$('button.btn-default:not(hidden)').length, "should display the validate button");
clientAction.destroy();
});
QUnit.test('Reconciliation partial', function (assert) {
assert.expect(9);
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, [
[6],
[{
partner_id: false,
counterpart_aml_dicts:[{
name: "BNK1/2017/0002: SUPP.OUT/2017/0002",
debit: 32.58,
credit: 0,
counterpart_aml_id: 392
}],
payment_aml_ids: [],
new_aml_dicts: []
}]
], "should call process_reconciliations with partial reconcile values");
}
return this._super(route, args);
},
});
clientAction.appendTo($('body'));
var 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.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, 1, "should display a partial reconciliation alert");
assert.ok(widget.$('.accounting_view .cell_left .line_info_button').hasClass('do_partial_reconcile_false'), "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'), "inactive", "should be inactive mode");
widget.$('button.btn-default:not(hidden)').trigger('click');
clientAction.destroy();
});
QUnit.test('Reconciliation title', function (assert) {
assert.expect(6);