[IMP] account: possibility to edit amount in reconciliation widget

Before we could only make a partial payment that was equal to the value of the statement line if the payment was greater than the statement line.
Now we can modify the value of any line (black line) used in the statement reconciliation widget. This allow to have the following:

Statement line of 500
Payment 1 of 500
Payment 2 of 500

We can reconcile the statement line with 250 from payment 1 and 250 from payment 2.

Task: 1870895

closes odoo/odoo#27805
This commit is contained in:
Cedric Snauwaert
2018-12-04 14:37:54 +00:00
parent 82ffd8b55f
commit 7d6a9924e5
7 changed files with 284 additions and 161 deletions
@@ -22,12 +22,12 @@ var StatementAction = AbstractAction.extend(ControlPanelMixin, {
change_offset: '_onAction',
change_partner: '_onAction',
add_proposition: '_onAction',
search_balance_amount: '_onAction',
remove_proposition: '_onAction',
update_proposition: '_onAction',
create_proposition: '_onAction',
getPartialAmount: '_onActionPartialAmount',
quick_create_proposition: '_onAction',
toggle_partial_reconcile: '_onAction',
partial_reconcile: '_onAction',
validate: '_onValidate',
change_name: '_onChangeName',
close_statement: '_onCloseStatement',
@@ -233,6 +233,14 @@ var StatementAction = AbstractAction.extend(ControlPanelMixin, {
});
},
_onActionPartialAmount: function(event) {
var self = this;
var handle = event.target.handle;
var line = this.model.getLine(handle);
var amount = this.model.getPartialReconcileAmount(handle, event.data);
self._getWidget(handle).updatePartialAmount(event.data.data, amount);
},
/**
* call 'changeName' model method
*
@@ -43,7 +43,7 @@ var _t = core._t;
* mode: string ('inactive', 'match', 'create')
* reconciliation_proposition: {
* id: number|string
* partial_reconcile: boolean
* partial_amount: number
* invalid: boolean - through the invalid line (without account, label...)
* is_tax: boolean
* account_code: string
@@ -533,24 +533,21 @@ var StatementModel = BasicModel.extend({
}
return $.when.apply($, defs);
},
searchBalanceAmount: function (handle) {
getPartialReconcileAmount: function(handle, data) {
var line = this.getLine(handle);
var amount = line.balance.amount;
var amount_str = _.str.sprintf('%.2f', Math.abs(amount));
amount_str = (amount > '0' ? '-' : '+') + amount_str;
if (line.balance.currency_id && line.balance.amount_currency) {
var amount_currency = line.balance.amount_currency;
var amount_currency_str = _.str.sprintf('%.2f', Math.abs(amount_currency));
amount_str += '|' + (amount_currency > '0' ? '-' : '+') + amount_currency_str;
var prop = _.find(line.reconciliation_proposition, {'id': data.data});
if (prop) {
var amount = prop.partial_amount || prop.amount;
// Check if we can get a partial amount that would directly set balance to zero
var partial = Math.abs(line.balance.amount + amount);
if (Math.abs(line.balance.amount) >= Math.abs(amount)) {
return Math.abs(amount);
}
if (partial <= Math.abs(prop.amount) && partial >= 0) {
return partial
}
return Math.abs(amount);
}
if (amount_str === line.filter) {
line.filter = '';
line.offset = 0;
return this.changeMode(handle, 'create');
}
line.filter = amount_str;
line.offset = 0;
return this.changeMode(handle, 'match');
},
/**
* Force the partial reconciliation to display the reconciliate button.
@@ -558,37 +555,35 @@ var StatementModel = BasicModel.extend({
* @param {string} handle
* @returns {Deferred}
*/
togglePartialReconcile: function (handle) {
partialReconcile: function(handle, data) {
var line = this.getLine(handle);
// Retrieve the toggle proposition
var selected = _.filter(line.reconciliation_proposition, function(prop){return prop.display_triangle});
// If no toggled proposition found, reject it
if(selected.length != 1)
return $.Deferred().reject();
selected = selected[0];
// 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 (selected.partial_reconcile) {
line.balance.amount = 0;
line.balance.type = 1;
line.mode = 'inactive';
var prop = _.find(line.reconciliation_proposition, {'id' : data.mvLineId});
if (prop) {
var amount = data.amount;
try {
amount = field_utils.parse.float(data.amount);
}
});
catch (err) {
amount = NaN;
}
// Amount can't be greater than line.amount and can not be negative and must be a number
// the amount we receive will be a string, so take sign of previous line amount in consideration in order to put
// the amount in the correct left or right column
if (amount >= Math.abs(prop.amount) || amount <= 0 || isNaN(amount)) {
delete prop.partial_amount_str;
delete prop.partial_amount;
if (isNaN(amount) || amount < 0) {
this.do_warn(_.str.sprintf(_t('The amount %s is not a valid partial amount'), data.amount));
}
return this._computeLine(line);
}
else {
var format_options = { currency_id: line.st_line.currency_id };
prop.partial_amount = (prop.amount > 0 ? 1 : -1)*amount;
prop.partial_amount_str = field_utils.format.monetary(Math.abs(prop.partial_amount), {}, format_options);
}
}
return this._computeLine(line);
},
/**
* Change the value of the editable proposition line or create a new one.
@@ -759,9 +754,6 @@ var StatementModel = BasicModel.extend({
}
line.reconciliation_proposition.push(prop);
_.each(line.reconciliation_proposition, function (prop) {
prop.partial_reconcile = false;
});
},
/**
* stop the editable proposition line and remove it if it's invalid then
@@ -830,6 +822,9 @@ var StatementModel = BasicModel.extend({
}
return;
}
if (!prop.already_paid && parseInt(prop.id)) {
prop.is_move_line = true;
}
reconciliation_proposition.push(prop);
if (prop.tax_id && prop.__tax_to_recompute && prop.base_amount) {
@@ -891,7 +886,7 @@ var StatementModel = BasicModel.extend({
_.each(reconciliation_proposition, function (prop) {
if (!prop.invalid) {
total -= prop.amount;
total -= prop.partial_amount || prop.amount;
if (isOtherCurrencyId) {
amount_currency -= (prop.amount < 0 ? -1 : 1) * Math.abs(prop.amount_currency);
}
@@ -915,6 +910,7 @@ var StatementModel = BasicModel.extend({
}) : false,
account_code: self.accounts[line.st_line.open_balance_account_id],
};
line.balance.show_balance = line.balance.amount_currency != 0;
line.balance.type = line.balance.amount_currency ? (line.st_line.partner_id ? 0 : -1) : 1;
});
},
@@ -1168,7 +1164,7 @@ var StatementModel = BasicModel.extend({
var line = this.getLine(handle);
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;
return _.isNumber(prop.id) ? prop.id : null;
});
})));
var filter = line.filter || "";
@@ -1200,8 +1196,8 @@ var StatementModel = BasicModel.extend({
*/
_formatToProcessReconciliation: function (line, prop) {
var amount = -prop.amount;
if (prop.partial_reconcile === true) {
amount = -prop.write_off_amount;
if (prop.partial_amount) {
amount = -prop.partial_amount;
}
var result = {
@@ -1459,6 +1455,9 @@ var ManualModel = StatementModel.extend({
_computeLine: function (line) {
return this._super(line).then(function () {
var props = _.reject(line.reconciliation_proposition, 'invalid');
_.each(line.reconciliation_proposition, function(p) {
delete p.is_move_line;
});
line.balance.type = -1;
if (!line.balance.amount_currency && props.length) {
line.balance.type = 1;
@@ -1564,7 +1563,7 @@ var ManualModel = StatementModel.extend({
var line = this.getLine(handle);
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;
return _.isNumber(prop.id) ? prop.id : null;
});
})));
var filter = line.filter || "";
@@ -254,14 +254,13 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
'click .accounting_view caption .o_buttons button': '_onValidate',
'click .accounting_view thead td': '_onTogglePanel',
'click .accounting_view tfoot td:not(.cell_left,.cell_right)': '_onShowPanel',
'click tfoot .cell_left, tfoot .cell_right': '_onSearchBalanceAmount',
'click .cell': '_onEditAmount',
'input input.filter': '_onFilterChange',
'click .match .load-more a': '_onLoadMore',
'click .match .mv_line td': '_onSelectMoveLine',
'click .accounting_view tbody .mv_line td': '_onSelectProposition',
'click .o_reconcile_models button': '_onQuickCreateProposition',
'click .create .add_line': '_onCreateProposition',
'click .accounting_view .line_info_button.fa-exclamation-triangle': '_onTogglePartialReconcile',
'click .reconcile_model_create': '_onCreateReconcileModel',
'click .reconcile_model_edit': '_onEditReconcileModel',
'keyup input': '_onInputKeyup',
@@ -284,7 +283,6 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
this.model = model;
this._initialState = state;
},
/**
* @override
*/
@@ -365,41 +363,15 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
// Search propositions that could be a partial credit/debit.
var props = [];
var partialDebitProp;
var partialCreditProp;
var balance = state.balance.amount_currency;
_.each(state.reconciliation_proposition, function (prop) {
if (prop.display) {
props.push(prop);
/*
Examples:
statement line | 100 | |
move line 1 | | 200 | <- can be a partial of 100
balance: -100
statement line | 500 | |
move line 1 | | 300 | <- is not a eligible to be a partial due to the second line.
move line 2 | | 300 | <- can be a partial of 200
balance: -100
statement line | 500 | |
move line 1 | | 700 | <- must not be a partial (debit = 800 > 700 = credit).
move line 2 | 300 | |
balance: 100
*/
if(!prop.display_new && balance < 0 && prop.amount > 0 && balance + prop.amount > 0)
partialDebitProp = prop;
else if(!prop.display_new && balance > 0 && prop.amount < 0 && balance + prop.amount < 0)
partialCreditProp = prop;
}
});
_.each(props, function (line) {
line.display_triangle = (line.already_paid === false &&
((state.balance.amount_currency < 0 || line.partial_reconcile) && partialDebitProp && partialDebitProp === line) ||
((state.balance.amount_currency > 0 || line.partial_reconcile) && partialCreditProp && partialCreditProp === line));
var $line = $(qweb.render("reconciliation.line.mv_line", {'line': line, 'state': state}));
var $line = $(qweb.render("reconciliation.line.mv_line", {'line': line, 'state': state, 'proposition': true}));
if (!isNaN(line.id)) {
$('<span class="line_info_button fa fa-info-circle"/>')
.appendTo($line.find('.cell_info_popover'))
@@ -477,6 +449,15 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
this.$('.create .add_line').toggle(!!state.balance.amount_currency);
},
updatePartialAmount: function(line_id, amount) {
var $line = $('.mv_line[data-line-id='+line_id+']');
$line.find('.edit_amount').addClass('d-none');
$line.find('.edit_amount_input').removeClass('d-none');
$line.find('.edit_amount_input').focus();
$line.find('.edit_amount_input').val(amount.toFixed(2));
$line.find('.line_amount').addClass('d-none');
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
@@ -625,6 +606,21 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
target: 'current'
});
},
_editAmount: function (event) {
event.stopPropagation();
var $line = $(event.target);
var moveLineId = $line.closest('.mv_line').data('line-id');
this.trigger_up('partial_reconcile', {'data': {mvLineId: moveLineId, 'amount': $line.val()}});
},
_onEditAmount: function (event) {
event.preventDefault();
event.stopPropagation();
// Don't call when clicking inside the input field
if (! $(event.target).hasClass('edit_amount_input')){
var $line = $(event.target);
this.trigger_up('getPartialAmount', {'data': $line.closest('.mv_line').data('line-id')});
}
},
/**
* @private
* @param {MouseEvent} event
@@ -664,12 +660,6 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
var mode = this.$el.data('mode') === 'inactive' ? 'match' : 'inactive';
this.trigger_up('change_mode', {'data': mode});
},
/**
* @private
*/
_onSearchBalanceAmount: function () {
this.trigger_up('search_balance_amount');
},
/**
* @private
*/
@@ -694,12 +684,24 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
return;
}
if(event.keyCode === 13) {
if ($(event.target).hasClass('edit_amount_input')) {
this.$(event.target).blur();
return;
}
var created_lines = _.findWhere(this.model.lines, {mode: 'create'});
if (created_lines && created_lines.balance.amount) {
this._onCreateProposition();
}
return;
}
if ($(event.target).hasClass('edit_amount_input')) {
if (event.type === 'keyup') {
return;
}
else {
return this._editAmount(event);
}
}
var self = this;
for (var fieldName in this.fields) {
@@ -772,16 +774,6 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
*/
_onValidate: function () {
this.trigger_up('validate');
},
/**
* @private
* @param {MouseEvent} event
*/
_onTogglePartialReconcile: function (e) {
e.stopPropagation();
var popover = $(e.target).data('bs.popover');
popover && popover.dispose();
this.trigger_up('toggle_partial_reconcile');
}
});
@@ -27,7 +27,21 @@ Tour.register('bank_statement_reconciliation', {
},
{
content: "click on partial reconcile",
trigger: '.o_reconciliation_line:last .accounting_view .do_partial_reconcile_true'
trigger: '.o_reconciliation_line:last .accounting_view .edit_amount',
},
{
content: "Edit amount",
trigger: '.o_reconciliation_line:last .accounting_view .edit_amount_input:not(.d-none)',
run: 'text 2000'
},
{
content: "Press enter to validate amount",
trigger: '.o_reconciliation_line:last .accounting_view .edit_amount_input:not(.d-none)',
run: 'keydown 13'
},
{
content: "Check that amount has changed",
trigger: '.o_reconciliation_line:last .accounting_view .line_amount:contains("2,000.00")'
},
{
content: "reconcile the line",
@@ -58,14 +58,17 @@
border-right: 1px solid #333;
padding-right: 5px;
}
.do_partial_reconcile_true {
color: $o-community-color; // FIXME probably supposed to be primary to be auto-adapted in enterprise ?
padding-right: 5px;
.edit_amount {
margin-left: 20px;
color: #bbb;
}
.do_partial_reconcile_false {
color: #f0eeee;
padding-right: 5px;
.cell:hover .edit_amount {
color: #00A09D;
}
.strike_amount {
text-decoration: line-through;
}
}
.o_multi_currency {
@@ -130,7 +130,7 @@
</t>
<t t-name="reconciliation.line.balance">
<tr t-if="state.balance.amount_currency &amp;&amp; !(state.reconciliation_proposition[0] || {}).partial_reconcile">
<tr t-if="state.balance.show_balance">
<td class="cell_action"><span role="img" class="toggle_create fa fa-play" aria-label="Run" title="Run"></span></td>
<td class="cell_account_code"><t t-esc="state.balance.account_code"/></td>
<td class="cell_due_date"></td>
@@ -220,17 +220,27 @@
</div>
<t t-name="reconciliation.line.mv_line.triangle">
<t t-if="line.display_triangle">
<t t-if="line.partial_reconcile">
<span t-att-class="'do_partial_reconcile_'+(!line.partial_reconcile)+' line_info_button fa fa-exclamation-triangle'"
data-content="Undo the partial reconciliation."/>
<t t-name="reconciliation.line.mv_line.amount">
<span t-att-class="(line.is_move_line &amp;&amp; proposition == true) ? 'cell' : ''">
<span class="line_amount">
<span t-if="line.amount_currency_str"
t-attf-class="o_multi_currency o_multi_currency_color_#{line.currency_id%8} line_info_button fa fa-money"
t-att-data-content="line.amount_currency_str"/>
<span t-if="line.partial_amount &amp;&amp; line.partial_amount != line.amount" class="strike_amount text-muted">
<t t-raw="line.amount_str"/>
<br/>
</span>
</span>
<t t-if="line.is_move_line &amp;&amp; proposition == true">
<i class="fa fa-pencil edit_amount"></i>
<input class="edit_amount_input text-right d-none"/>
</t>
<t t-else="">
<span t-att-class="'do_partial_reconcile_'+(!line.partial_reconcile)+' line_info_button fa fa-exclamation-triangle'"
data-content="This move's amount is higher than the transaction's amount. Click to register a partial payment and keep the payment balance open."/>
</t>
</t>
<span class="line_amount">
<t t-if="!line.partial_amount_str" t-raw="line.amount_str"/>
<t t-if="line.partial_amount_str &amp;&amp; line.partial_amount != line.amount" t-raw="line.partial_amount_str"/>
</span>
</span>
</t>
@@ -256,20 +266,12 @@
</td>
<td class="cell_left">
<t t-if="line.amount &lt; 0">
<t t-call="reconciliation.line.mv_line.triangle"/>
<span t-if="line.amount_currency_str"
t-attf-class="o_multi_currency o_multi_currency_color_#{line.currency_id%8} line_info_button fa fa-money"
t-att-data-content="line.amount_currency_str"/>
<t t-raw="line.partial_reconcile ? line.write_off_amount_str : line.amount_str"/>
<t t-call="reconciliation.line.mv_line.amount"/>
</t>
</td>
<td class="cell_right">
<t t-if="line.amount &gt; 0">
<t t-call="reconciliation.line.mv_line.triangle"/>
<span t-if="line.amount_currency_str"
t-attf-class="o_multi_currency o_multi_currency_color_#{line.currency_id%8} line_info_button fa fa-money"
t-att-data-content="line.amount_currency_str"/>
<t t-raw="line.partial_reconcile ? line.write_off_amount_str : line.amount_str"/>
<t t-call="reconciliation.line.mv_line.amount"/>
</t>
</td>
<td class="cell_info_popover"></td>
@@ -423,11 +423,11 @@ var mv_lines = {
'[6,"",0,5]': [
{'account_type': "liquidity", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-01-23", 'date': "2017-01-23", 'total_amount_str': "$ 376.00", 'partner_id': 7, 'account_name': "Bank", 'name': "BNK1/2017/0002: SUPP.OUT/2017/0002", 'partner_name': "ASUSTeK", 'total_amount_currency_str': "", 'id': 392, 'credit': 376.0, 'journal_id': "Bank", 'amount_str': "$ 376.00", 'debit': 0.0, 'account_code': "101401", 'ref': "BILL/2017/0003", 'already_paid': true},
{'account_type': "liquidity", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-01-23", 'date': "2017-01-23", 'total_amount_str': "$ 100.00", 'partner_id': 8, 'account_name': "Bank", 'name': "BNK1/2017/0003: CUST.IN/2017/0001", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 394, 'credit': 0.0, 'journal_id': "Bank", 'amount_str': "$ 100.00", 'debit': 100.0, 'account_code': "101401", 'ref': "", 'already_paid': true},
{'account_type': "payable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-28", 'date': "2017-01-01", 'total_amount_str': "$ 10,000.00", 'partner_id': 12, 'account_name': "Account Payable", 'name': "BILL/2017/0001", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 114, 'credit': 10000.0, 'journal_id': [2, "Vendor Bills"], 'amount_str': "$ 10,000.00", 'debit': 0.0, 'account_code': "111100", 'ref': "", 'already_paid': false},
{'account_type': "liquidity", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-01-23", 'date': "2017-01-23", 'total_amount_str': "$ 525.50", 'partner_id': 8, 'account_name': "Bank", 'name': "BNK1/2017/0004: CUST.IN/2017/0002", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 396, 'credit': 0.0, 'journal_id': "Bank", 'amount_str': "$ 525.50", 'debit': 525.5, 'account_code': "101401", 'ref': "INV/2017/0003", 'already_paid': true},
{'account_type': "receivable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-07", 'date': "2017-01-08", 'total_amount_str': "$ 650.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': "$ 650.00", 'debit': 650.0, 'account_code': "101200", 'ref': "", 'already_paid': false},
{'account_type': "receivable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-22", 'date': "2017-01-23", 'total_amount_str': "$ 525.00", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0004", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 399, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 525.00", 'debit': 525.0, 'account_code': "101200", 'ref': "", 'already_paid': false},
{'account_type': "receivable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-28", 'date': "2017-01-01", 'total_amount_str': "$ 4,610.00", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0001", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 106, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 4,610.00", 'debit': 4610.0, 'account_code': "101200", 'ref': "", 'already_paid': false},
{'account_type': "payable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-28", 'date': "2017-01-01", 'total_amount_str': "$ 10,000.00", 'partner_id': 12, 'account_name': "Account Payable", 'name': "BILL/2017/0001", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 114, 'credit': 10000.0, 'journal_id': [2, "Vendor Bills"], 'amount_str': "$ 10,000.00", 'debit': 0.0, 'account_code': "111100", 'ref': "", 'already_paid': false},
{'account_type': "payable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-28", 'date': "2017-01-15", 'total_amount_str': "$ 5,749.99", 'partner_id': 7, 'account_name': "Account Payable", 'name': "BILL/2017/0002", 'partner_name': "ASUSTeK", 'total_amount_currency_str': "", 'id': 117, 'credit': 5749.99, 'journal_id': [2, "Vendor Bills"], 'amount_str': "$ 5,749.99", 'debit': 0.0, 'account_code': "111100", 'ref': "", 'already_paid': false}
],
'[6,"",5,5]': [
@@ -689,12 +689,12 @@ QUnit.module('account', {
clientAction.widgets[1].$('.accounting_view thead td:first').trigger('click');
assert.containsN(clientAction.widgets[1], '.mv_line', 5, "should display 5 account move lines");
assert.strictEqual(clientAction.widgets[1].$('.mv_line .cell_right:contains(".")').length, 4, "should display only the credit account move lines (hide the debit)");
assert.strictEqual(clientAction.widgets[1].$('.mv_line .cell_right:contains(".")').length, 3, "should display only the credit account move lines (hide the debit)");
assert.containsN(clientAction.widgets[1], '.mv_line.already_reconciled', 3, "should display 3 already reconciled account move lines");
assert.strictEqual(clientAction.widgets[1].$('.mv_line').text().replace(/[\n\r\s]+/g, ' '),
" 101401 2017-01-23 ASUSTeK: BNK1/2017/0002: SUPP.OUT/2017/0002 : BILL/2017/0003 $ 376.00 101401 2017-01-23 Agrolait: BNK1/2017/0003: CUST.IN/2017/0001 $ 100.00 101401 2017-01-23 Agrolait: BNK1/2017/0004: CUST.IN/2017/0002 : INV/2017/0003 $ 525.50 101200 2017-02-07 Agrolait: INV/2017/0002 $ 650.00 101200 2017-02-22 Agrolait: INV/2017/0004 $ 525.00 ",
" 101401 2017-01-23 ASUSTeK: BNK1/2017/0002: SUPP.OUT/2017/0002 : BILL/2017/0003 $ 376.00 101401 2017-01-23 Agrolait: BNK1/2017/0003: CUST.IN/2017/0001 $ 100.00 111100 2017-02-28 Camptocamp: BILL/2017/0001 $ 10,000.00 101401 2017-01-23 Agrolait: BNK1/2017/0004: CUST.IN/2017/0002 : INV/2017/0003 $ 525.50 101200 2017-02-07 Agrolait: INV/2017/0002 $ 650.00 ",
"should display 4 account move lines who contains the account_code, due_date, label and the credit");
assert.strictEqual(clientAction.widgets[1].$('.mv_line .cell_left:contains(".")').length, 1, "should display only 1 debit account move lines");
assert.strictEqual(clientAction.widgets[1].$('.mv_line .cell_left:contains(".")').length, 2, "should display only 2 debit account move lines");
// load more
assert.ok(clientAction.widgets[1].$('.match div.load-more a:visible').length, "should display the 'load more' button");
@@ -846,7 +846,7 @@ QUnit.module('account', {
});
QUnit.test('Reconciliation partial', function (assert) {
assert.expect(10);
assert.expect(9);
var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options);
testUtils.mock.addMockEnvironment(clientAction, {
@@ -855,12 +855,19 @@ QUnit.module('account', {
console.log(args.method);
if (args.method === 'process_bank_statement_line') {
var lines = args.args['1'];
console.log(args.arsg);
assert.deepEqual(args.args, [
[6],
[{
partner_id: lines.length == 1 ? lines[0].partner_id : false,
counterpart_aml_dicts:[],
payment_aml_ids: [392],
counterpart_aml_dicts:[{
"analytic_tag_ids": [[6, null, []]],
"counterpart_aml_id": 114,
"credit": 0,
"debit": 32.58,
"name": "BILL/2017/0001"
}],
payment_aml_ids: [],
new_aml_dicts: [],
}]
], "should call process_bank_statement_line with partial reconcile values");
@@ -883,26 +890,29 @@ QUnit.module('account', {
var widget = clientAction.widgets[0];
widget.$('.match .cell_account_code:first').trigger('click');
assert.notOk( widget.$('.cell_right .line_info_button').length, "should not display the partial reconciliation alert");
assert.equal( widget.$('.cell_right .edit_amount').length, 1, "should display the edition pencil");
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.strictEqual(widget.$('.match .mv_line[data-line-id=114] .cell_account_code:first()').length, 1, 'Should have line')
widget.$('.match .mv_line[data-line-id=114] .cell_account_code').trigger('click');
assert.equal( widget.$('.accounting_view tbody .cell_left .edit_amount').length, 1, "should display the edition pencil");
// The partner has been set automatically, remove it.
widget.$('.o_input_dropdown input').trigger('click');
widget.$('.o_input_dropdown input').val('').trigger('keyup').trigger('blur');
assert.containsNone(widget, 'button.btn-primary:visible', "should display the reconcile model buttons");
assert.containsOnce(widget, '.text-danger:visible', "should display counterpart alert");
widget.$('.accounting_view .cell_left .line_info_button').trigger('click');
assert.containsOnce(widget, '.accounting_view .cell_left .line_info_button', "should display a partial reconciliation alert");
assert.doesNotHaveClass(widget.$('.accounting_view .cell_left .line_info_button'), 'do_partial_reconcile_true', "should display the partial reconciliation information");
assert.containsOnce(widget, 'button.btn-primary:visible', "should display the reconcile button");
assert.strictEqual( widget.$el.data('mode'), "inactive", "should be inactive mode");
assert.strictEqual(widget.$('button.btn-primary:visible').length, 0, "should display the reconcile model buttons");
assert.strictEqual(widget.$('.text-danger:visible').length, 1, "should display counterpart alert");
widget.$('.accounting_view .cell_left .edit_amount').trigger('click');
assert.strictEqual(widget.$('.accounting_view .cell_left .edit_amount_input:not(.d-none)').length, 1, "should display the input field to edit amount");
// Edit amount
widget.$('.accounting_view .cell_left .edit_amount_input:not(.d-none)').val('32.58').trigger('blur');
assert.strictEqual(widget.$('.accounting_view .cell_left .line_amount').text().replace(/[\n\r\s]+/g, ' '), " $ 10000.00 $ 32.58 ", "should display previous amount and new amount")
assert.strictEqual(widget.$('button.btn-primary:visible').length, 1, "should display the reconcile button");
widget.$('button.btn-primary:visible').trigger('click');
clientAction.destroy();
@@ -1320,7 +1330,7 @@ QUnit.module('account', {
});
QUnit.test('Reconciliation: Payment < inv1 + inv2(partial)', function (assert) {
assert.expect(4);
assert.expect(3);
/*
* One payment: $1175
@@ -1383,17 +1393,15 @@ QUnit.module('account', {
// 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");
assert.ok( widget.$('.cell_right .edit_amount').length,
"should display the pencil to edit amount");
// 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");
testUtils.dom.click($reconciliationAlert);
// Edit amount on last invoice
testUtils.dom.click(widget.$('.edit_amount:last()'));
widget.$('.edit_amount_input:last()').val('525').trigger('blur');
var $buttonReconcile = widget.$('button.o_reconcile:not(hidden)');
@@ -1484,13 +1492,13 @@ QUnit.module('account', {
// 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");
assert.ok(widget.$('.cell_right .edit_amount').length,
"should display the pencil to edit amount");
// 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");
assert.ok(widget.$('.cell_right .edit_amount').length,
"should display the pencil to edit amount");
var writeOffCreate = widget.$('div.create');
@@ -1513,6 +1521,103 @@ QUnit.module('account', {
clientAction.destroy();
});
QUnit.test('Reconciliation: partial payment of 2 invoices with one payment', function (assert) {
assert.expect(4);
/*
* One payment: $1175
* Two Invoices as Inv1 = 1200; Inv2 = 1200:
* Payment < Inv1 AND Payment < Inv2
* Assign 500 to inv1 and 675 to inv2
*/
// modify the invoice line to have their amount > payment
var indexInv1 = _.findIndex(this.params.mv_lines['[5,"",0,5]'], function (line) {return line.id === 109});
this.params.mv_lines['[5,"",0,5]'][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,5]'], function (line) {return line.id === 112});
this.params.mv_lines['[5,"",0,5]'][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_bank_statement_line') {
assert.deepEqual(args.args,
[
[5], // Id of the bank statement line
[{counterpart_aml_dicts:
[{name:"INV/2017/0002",
debit: 0,
credit: 500,
analytic_tag_ids: [[6, null, []]],
counterpart_aml_id: 109},
{name: "INV/2017/0003",
debit: 0,
credit: 675,
analytic_tag_ids: [[6, null, []]],
counterpart_aml_id: 112}],
payment_aml_ids: [],
partner_id: 8,
new_aml_dicts: []
}]
], "should call process_bank_statement_line with correct counterpart_aml_dicts");
}
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 .edit_amount').length,
"should display the pencil to edit amount");
// Add second invoice
widget.$('.match .cell_account_code:first').trigger('click');
assert.ok(widget.$('.cell_right .edit_amount').length,
"should display the pencil to edit amount");
// Edit invoice first amount
widget.$('.edit_amount:first()').trigger('click');
widget.$('.edit_amount_input:first()').val('500').trigger('blur');
// Edit invoice second amount
var $buttonReconcile = widget.$('button.o_reconcile:not(hidden)');
widget.$('.edit_amount:last()').trigger('click');
widget.$('.edit_amount_input:last()').val('675').trigger('blur');
assert.equal($buttonReconcile.length, 1,
'The reconcile button must be visible');
$buttonReconcile.click();
clientAction.destroy();
});
QUnit.test('Manual Reconciliation: remove a prop to attain balance and reconcile', function (assert) {
assert.expect(5);
@@ -1559,7 +1664,7 @@ QUnit.module('account', {
// Add first prop
widget.$('.match .cell_account_code:first').trigger('click');
assert.equal( widget.$('.cell_right .line_info_button').length, 1, "should display the partial reconciliation alert");
assert.equal( widget.$('.cell_right .edit_amount').length, 0, "should not display the pencil to edit amount");
// Add second prop
widget.$('.match .cell_account_code:first').trigger('click');