diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js
index 791d94e515d..f050ebfa8e5 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_model.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js
@@ -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;
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
index 011591afa29..541b3a1952a 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js
@@ -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.");
+ }
+
$('')
- .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);
},
diff --git a/addons/account/static/src/less/account_reconciliation.less b/addons/account/static/src/less/account_reconciliation.less
index 5df97bb2e9f..253dc25996f 100644
--- a/addons/account/static/src/less/account_reconciliation.less
+++ b/addons/account/static/src/less/account_reconciliation.less
@@ -176,6 +176,7 @@
vertical-align: top;
padding-right: 10px;
font-weight: bold;
+ white-space: nowrap;
}
}
diff --git a/addons/account/static/src/xml/account_reconciliation.xml b/addons/account/static/src/xml/account_reconciliation.xml
index 07a9bbfea9d..33a08794bbc 100644
--- a/addons/account/static/src/xml/account_reconciliation.xml
+++ b/addons/account/static/src/xml/account_reconciliation.xml
@@ -112,7 +112,7 @@
-
+
|
|
|
@@ -211,7 +211,8 @@
| Partner | |
| Date | |
| Due Date | |
- | ResidualAmount |
+ |
| Amount | () |
+ | Residual |
()
|
diff --git a/addons/account/static/test/reconciliation_tests.js b/addons/account/static/test/reconciliation_tests.js
index 62ec17f9740..2e984dcf878 100644
--- a/addons/account/static/test/reconciliation_tests.js
+++ b/addons/account/static/test/reconciliation_tests.js
@@ -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);