[IMP] account, bank statement reconciliation widget: improve suggestions

Suggestions made to reconcile bank statement lines are now more accurate (based on communication and amount)
There's no automatic mode anymore (button + <CTRL+ENTER> shortcut removed)
Partners are set automatically when selecting a line in the widget (that will help the creation of a res.partner.bank for him, for a better partner detection next time)

Was task: 1847703
Was PR #25438
This commit is contained in:
Laurent Smet
2018-08-17 17:35:39 +02:00
committed by qdp-odoo
parent 6d53cd3201
commit b0a29c9bad
9 changed files with 379 additions and 333 deletions
@@ -398,86 +398,6 @@ class AccountBankStatementLine(models.Model):
return select_clause, from_clause, where_clause
return select_clause + from_clause + where_clause
# todo: remove this => coverage task
def _get_move_lines_for_auto_reconcile(self):
""" Returns the move lines that the method auto_reconcile can use to try to reconcile the statement line """
pass
# this method is only call by the widget reconciliation, move it or not ?
@api.multi
def auto_reconcile(self):
""" Try to automatically reconcile the statement.line ; return the counterpart journal entry/ies if the automatic reconciliation succeeded, False otherwise.
TODO : this method could be greatly improved and made extensible
"""
self.ensure_one()
match_recs = self.env['account.move.line']
amount = self.amount_currency or self.amount
company_currency = self.journal_id.company_id.currency_id
st_line_currency = self.currency_id or self.journal_id.currency_id
currency = (st_line_currency and st_line_currency != company_currency) and st_line_currency.id or False
precision = st_line_currency and st_line_currency.decimal_places or company_currency.decimal_places
params = {'company_id': self.env.user.company_id.id,
'account_payable_receivable': (self.journal_id.default_credit_account_id.id, self.journal_id.default_debit_account_id.id),
'amount': float_round(amount, precision_digits=precision),
'partner_id': self.partner_id.id,
'ref': self.name,
}
field = currency and 'amount_residual_currency' or 'amount_residual'
liquidity_field = currency and 'amount_currency' or amount > 0 and 'debit' or 'credit'
# Look for structured communication match
if self.name:
sql_query = self._get_common_sql_query() + \
" AND aml.ref = %(ref)s AND ("+field+" = %(amount)s OR (acc.internal_type = 'liquidity' AND "+liquidity_field+" = %(amount)s)) \
ORDER BY date_maturity asc, aml.id asc"
self.env.cr.execute(sql_query, params)
match_recs = self.env.cr.dictfetchall()
if len(match_recs) > 1:
return False
# Look for a single move line with the same partner, the same amount
if not match_recs:
if self.partner_id:
sql_query = self._get_common_sql_query() + \
" AND ("+field+" = %(amount)s OR (acc.internal_type = 'liquidity' AND "+liquidity_field+" = %(amount)s)) \
ORDER BY date_maturity asc, aml.id asc"
self.env.cr.execute(sql_query, params)
match_recs = self.env.cr.dictfetchall()
if len(match_recs) > 1:
return False
if not match_recs:
return False
match_recs = self.env['account.move.line'].browse([aml.get('id') for aml in match_recs])
# Now reconcile
counterpart_aml_dicts = []
payment_aml_rec = self.env['account.move.line']
for aml in match_recs:
if aml.account_id.internal_type == 'liquidity':
payment_aml_rec = (payment_aml_rec | aml)
else:
amount = aml.currency_id and aml.amount_residual_currency or aml.amount_residual
counterpart_aml_dicts.append({
'name': aml.name if aml.name != '/' else aml.move_id.name,
'debit': amount < 0 and -amount or 0,
'credit': amount > 0 and amount or 0,
'move_line': aml
})
try:
with self._cr.savepoint():
counterpart = self.process_reconciliation(counterpart_aml_dicts=counterpart_aml_dicts, payment_aml_rec=payment_aml_rec)
return counterpart
except UserError:
# A configuration / business logic error that makes it impossible to auto-reconcile should not be raised
# since automatic reconciliation is just an amenity and the user will get the same exception when manually
# reconciling. Other types of exception are (hopefully) programming errors and should cause a stacktrace.
self.invalidate_cache()
self.env['account.move'].invalidate_cache()
self.env['account.move.line'].invalidate_cache()
return False
def _prepare_reconciliation_move(self, move_ref):
""" Prepare the dict of values to create the move from a statement line. This method may be overridden to adapt domain logic
through model inheritance (make sure to call super() to establish a clean extension chain).
+232 -105
View File
@@ -3,8 +3,7 @@
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.osv import expression
from odoo.tools import pycompat
from odoo.tools import float_round, float_repr
from odoo.tools import pycompat, float_is_zero
from odoo.tools.misc import formatLang
@@ -12,52 +11,236 @@ class AccountReconciliation(models.AbstractModel):
_name = 'account.reconciliation.widget'
####################################################
# Public
# Search propositions
####################################################
@api.model
def auto_reconcile(self, st_line_ids, num_already_reconciled_lines=0):
""" Use statement line auto_reconcile method and return the details
to the widget interface.
def _get_matching_amls_query(self, st_lines, excluded_ids=None):
''' Base query used by the matching rules.
Main things about this huge query:
- Try to join the res.partner table:
1) On st_line.partner_id.
2) On partner_id found on a res.partner.bank sharing the same account number.
3) On a partner having the same case insensitive name.
- Basic filters on account_move_line.
:param st_line_ids: ids of the statement lines to reconciliate
:param num_already_reconciled_lines: number of reconcilied lines
used to increment the progress bar in the interface in function
of the real number of reconcilied lines.
"""
st_lines = self.env['account.bank.statement.line'].browse(st_line_ids)
automatic_reconciliation_entries = self.env['account.bank.statement.line']
unreconciled = self.env['account.bank.statement.line']
:param st_lines: Account.bank.statement.lines recordset.
:param excluded_ids: Account.move.lines to exclude.
:return: (query, params)
'''
params = [tuple(st_lines.ids)]
for stl in st_lines:
res = stl.auto_reconcile()
if res:
automatic_reconciliation_entries += stl
else:
unreconciled += stl
# N.B: The first part of the CASE is about 'blue lines' while the second part is about 'black lines'.
query = '''
SELECT
st_line.id AS id,
aml.id AS aml_id,
aml.currency_id AS aml_currency_id,
aml.amount_residual AS aml_amount_residual,
aml.amount_residual_currency AS aml_amount_residual_currency
FROM account_bank_statement_line st_line
LEFT JOIN account_journal journal ON journal.id = st_line.journal_id
LEFT JOIN res_company company ON company.id = st_line.company_id
LEFT JOIN res_partner_bank bank ON bank.id = st_line.bank_account_id OR bank.acc_number = st_line.account_number
LEFT JOIN res_partner partner ON (
CASE WHEN st_line.partner_id IS NOT NULL THEN
partner.id = st_line.partner_id
WHEN bank.partner_id IS NOT NULL THEN
partner.id = bank.partner_id
ELSE
partner.name ILIKE st_line.partner_name
END
)
, account_move_line aml
LEFT JOIN res_company aml_company ON aml_company.id = aml.company_id
LEFT JOIN account_account aml_account ON aml_account.id = aml.account_id
WHERE st_line.id IN %s
AND aml.company_id = st_line.company_id
AND aml.statement_id IS NULL
AND (
company.account_bank_reconciliation_start IS NULL
OR
aml.date > company.account_bank_reconciliation_start
)
AND CASE WHEN journal.default_credit_account_id IS NOT NULL
AND journal.default_debit_account_id IS NOT NULL
THEN
(
aml.account_id IN (journal.default_credit_account_id, journal.default_debit_account_id)
AND aml.payment_id IS NOT NULL
)
OR
(
aml_account.reconcile IS TRUE
AND aml.reconciled IS FALSE
)
END
'''
# Collect various information for the reconciliation widget
notifications = []
num_auto_reconciled = len(automatic_reconciliation_entries)
if num_auto_reconciled > 0:
auto_reconciled_message = num_auto_reconciled > 1 \
and _("%d transactions were automatically reconciled.") % num_auto_reconciled \
or _("1 transaction was automatically reconciled.")
notifications += [{
'type': 'info',
'message': auto_reconciled_message,
'details': {
'name': _("Automatically reconciled items"),
'model': 'account.move',
'ids': automatic_reconciliation_entries.mapped('journal_entry_ids').mapped('move_id').ids
}
}]
return {
'st_lines_ids': unreconciled.ids,
'notifications': notifications,
'statement_name': False,
'num_already_reconciled_lines': num_auto_reconciled + num_already_reconciled_lines,
}
if excluded_ids:
query += 'AND aml.id NOT IN %s'
params.append(tuple(excluded_ids))
return query, params
@api.model
def _get_matching_amls_invoice_rule(self, st_lines, excluded_ids=None):
''' RULE 1: Match an account.move.line automatically if linked to an invoice having a number or reference quite
similar.
This rule automatically match when invoice_reference match when:
- matching only one invoice.
- matching multiple invoices but having the same total amount residual.
:param st_lines: Account.bank.statement.lines recordset.
:param excluded_ids: Account.move.lines to exclude.
:return: (query, params, automatic_match_func)
'''
def automatic_match_func(st_line, fetched_amls):
# Match only one invoice.
if len(fetched_amls) == 1:
return True
# Match multiple invoices but having the same total amount residual.
total_residual = sum(aml['aml_currency_id'] and aml['aml_amount_residual_currency'] or aml['aml_amount_residual'] for aml in fetched_amls)
line_residual = st_line.currency_id and st_line.amount_currency or st_line.amount
line_currency = st_line.currency_id or st_line.journal_id.currency_id or st_line.company_id.currency_id
return float_is_zero(total_residual - line_residual, precision_rounding=line_currency.rounding)
query, params = self._get_matching_amls_query(st_lines, excluded_ids=excluded_ids)
# Join the account_invoice table.
query = query.replace(
'account_move_line aml',
'''
account_move_line aml
LEFT JOIN account_move move ON move.id = aml.move_id
LEFT JOIN account_invoice invoice ON invoice.move_name = move.name
'''
)
# Add where clause.
# N.B: invoice_reference could be a list of invoice reference/number (e.g. 'INV/2018/0001,INV/2018/0002').
query += '''
AND invoice.state = 'open'
AND CASE WHEN st_line.amount >= 0.0 THEN
invoice.type IN ('out_invoice', 'in_refund')
ELSE
invoice.type IN ('in_invoice', 'out_refund')
END
AND (
REGEXP_REPLACE(st_line.name, '[^0-9]', '', 'g') ~ REGEXP_REPLACE(invoice.number, '[^0-9]', '', 'g')
OR (
invoice.reference IS NOT NULL
AND
REGEXP_REPLACE(st_line.name, '[^0-9]', '', 'g') ~ REGEXP_REPLACE(invoice.reference, '[^0-9]', '', 'g')
)
)
AND CASE WHEN partner.id IS NOT NULL THEN
invoice.partner_id = partner.id
ELSE
TRUE
END
'''
return query, params, automatic_match_func
@api.model
def _get_matching_amls_amount_rule(self, st_lines, excluded_ids=None):
''' RULE 2: Match one or more account.move.lines automatically if either the statement line has exactly the same amount
or either the statement line matchs a single line having a greater or equals amount.
This only work if a partner has been found on the statement line.
:param st_lines: Account.bank.statement.lines recordset.
:param excluded_ids: Account.move.lines to exclude.
:return: (query, params, automatic_match_func)
'''
def automatic_match_func(st_line, fetched_amls):
total_residual = sum(aml['aml_currency_id'] and aml['aml_amount_residual_currency'] or aml['aml_amount_residual'] for aml in fetched_amls)
line_residual = st_line.currency_id and st_line.amount_currency or st_line.amount
line_currency = st_line.currency_id or st_line.journal_id.currency_id or st_line.company_id.currency_id
# Match the total residual amount.
if float_is_zero(total_residual - line_residual, precision_rounding=line_currency.rounding):
return True
# Match only one line having a greater residual amount.
return len(fetched_amls) == 1 and line_residual < total_residual
query, params = self._get_matching_amls_query(st_lines, excluded_ids=excluded_ids)
# Add where clause.
# N.B: move.line currency_id is either set or either got from the company.
# N.B2: statement.line currency_id is either set, either got from the journal or either got from the company.
query += '''
AND partner.id IS NOT NULL
AND aml.partner_id = partner.id
AND (
CASE WHEN st_line.currency_id IS NOT NULL AND st_line.currency_id != aml_company.currency_id THEN
aml.currency_id = st_line.currency_id
WHEN journal.currency_id IS NOT NULL AND journal.currency_id != aml_company.currency_id THEN
aml.currency_id = journal.currency_id
ELSE
aml.currency_id IS NULL
END
)
'''
return query, params, automatic_match_func
@api.model
def _get_matching_amls(self, st_lines, excluded_ids=None):
''' Apply reconciliation matching rules in order to find matching account.move.lines.
:param st_lines: Account.bank.statement.lines recordset.
:param excluded_ids: Account.move.lines ids to exclude.
:return: A dictionnary mapping each id with:
* line: The account.bank.statement.line record.
* aml_ids: The matching account.move.line ids.
'''
results = dict((r.id, {'line': r, 'aml_ids': []}) for r in st_lines)
excluded_ids = excluded_ids or []
rules = [
self._get_matching_amls_invoice_rule,
self._get_matching_amls_amount_rule,
]
for rule in rules:
query, params, automatic_match_func = rule(st_lines, excluded_ids=excluded_ids)
self._cr.execute(query, params)
query_res = self._cr.dictfetchall()
# Map statement line with candidates.
candidates_map = {}
for res in query_res:
candidates_map.setdefault(res['id'], [])
candidates_map[res['id']].append(res)
for line_id, fetched_amls in candidates_map.items():
st_line = results[line_id]['line']
candidate_amls = []
candidate_amls_ids = []
for aml in fetched_amls:
if aml['aml_id'] not in excluded_ids:
candidate_amls.append(aml)
candidate_amls_ids.append(aml['aml_id'])
if automatic_match_func(results[line_id]['line'], candidate_amls):
results[line_id]['aml_ids'] = candidate_amls_ids
# Mark statement line as already processed.
st_lines -= st_line
# Exclude move lines.
excluded_ids += candidate_amls_ids
return results
####################################################
# Public
####################################################
@api.model
def process_bank_statement_line(self, st_line_ids, data):
@@ -133,15 +316,15 @@ class AccountReconciliation(models.AbstractModel):
excluded_ids = excluded_ids or []
ret = []
st_lines = self.env['account.bank.statement.line'].browse(st_line_ids)
matching_amls = self._get_matching_amls(st_lines, excluded_ids=excluded_ids)
for st_line in st_lines:
aml_recs = self._get_statement_line_reconciliation_proposition(st_line, excluded_ids=excluded_ids)
target_currency = st_line.currency_id or st_line.journal_id.currency_id or st_line.journal_id.company_id.currency_id
rp = self._prepare_move_lines(aml_recs, target_currency=target_currency, target_date=st_line.date)
excluded_ids += [move_line['id'] for move_line in rp]
# Iterate on st_lines to keep the same order in the results list.
for line in st_lines:
aml_ids = matching_amls[line.id]['aml_ids']
amls = aml_ids and self.env['account.move.line'].browse(aml_ids)
ret.append({
'st_line': self._get_statement_line(st_line),
'reconciliation_proposition': rp
'st_line': self._get_statement_line(line),
'reconciliation_proposition': aml_ids and self._prepare_move_lines(amls) or [],
})
return ret
@@ -641,62 +824,6 @@ class AccountReconciliation(models.AbstractModel):
return data
@api.model
def _get_statement_line_reconciliation_proposition(self, st_line, excluded_ids=None):
""" Returns move lines that constitute the best guess to reconcile a statement line
Note: it only looks for move lines in the same currency as the statement line.
"""
Account_move_line = self.env['account.move.line']
if not excluded_ids:
excluded_ids = []
amount = st_line.amount_currency or st_line.amount
company_currency = st_line.journal_id.company_id.currency_id
st_line_currency = st_line.currency_id or st_line.journal_id.currency_id
currency = (st_line_currency and st_line_currency != company_currency) and st_line_currency.id or False
precision = st_line_currency and st_line_currency.decimal_places or company_currency.decimal_places
params = {'company_id': st_line.company_id.id,
'account_payable_receivable': (st_line.journal_id.default_credit_account_id.id, st_line.journal_id.default_debit_account_id.id),
'amount': float_repr(float_round(amount, precision_digits=precision), precision_digits=precision),
'partner_id': st_line.partner_id.id,
'excluded_ids': tuple(excluded_ids),
'ref': st_line.name,
'bank_rec_threshold': st_line.journal_id.company_id.account_bank_reconciliation_start,
}
# Look for structured communication match
if st_line.name:
add_to_select = ", CASE WHEN aml.ref = %(ref)s THEN 1 ELSE 2 END as temp_field_order "
add_to_from = " JOIN account_move m ON m.id = aml.move_id "
select_clause, from_clause, where_clause = st_line._get_common_sql_query(overlook_partner=True, excluded_ids=excluded_ids, split=True)
sql_query = select_clause + add_to_select + from_clause + add_to_from + where_clause
if params['bank_rec_threshold']:
sql_query += "AND aml.date > %(bank_rec_threshold)s"
sql_query += " AND (aml.ref= %(ref)s or m.name = %(ref)s) \
ORDER BY temp_field_order, date_maturity desc, aml.id desc"
self.env.cr.execute(sql_query, params)
results = self.env.cr.fetchone()
if results:
return Account_move_line.browse(results[0])
# Look for a single move line with the same amount
field = currency and 'amount_residual_currency' or 'amount_residual'
liquidity_field = currency and 'amount_currency' or amount > 0 and 'debit' or 'credit'
liquidity_amt_clause = currency and '%(amount)s::numeric' or 'abs(%(amount)s::numeric)'
sql_query = st_line._get_common_sql_query(excluded_ids=excluded_ids) + \
" AND (" + field + " = %(amount)s::numeric OR (acc.internal_type = 'liquidity' AND " + liquidity_field + " = " + liquidity_amt_clause + "))"
if params['bank_rec_threshold']:
sql_query += "AND aml.date > %(bank_rec_threshold)s "
sql_query += "ORDER BY date_maturity desc, aml.id desc LIMIT 1"
self.env.cr.execute(sql_query, params)
results = self.env.cr.fetchone()
if results:
return Account_move_line.browse(results[0])
return Account_move_line
@api.model
def _get_move_line_reconciliation_proposition(self, account_id, partner_id=None):
""" Returns two lines whose amount are opposite """
@@ -27,9 +27,7 @@ var StatementAction = AbstractAction.extend(ControlPanelMixin, {
create_proposition: '_onAction',
quick_create_proposition: '_onAction',
toggle_partial_reconcile: '_onAction',
auto_reconciliation: '_onValidate',
validate: '_onValidate',
validate_all_balanced: '_onValidate',
change_name: '_onChangeName',
close_statement: '_onCloseStatement',
load_more: '_onLoadMore',
@@ -271,7 +269,7 @@ var StatementAction = AbstractAction.extend(ControlPanelMixin, {
return this._loadMore(this.model.defaultDisplayQty);
},
/**
* call 'validate' or 'autoReconciliation' model method then destroy the
* call 'validate' model method then destroy the
* validated lines and update the action renderer with the new status bar
* values and notifications then open the first available line
*
@@ -281,7 +279,7 @@ var StatementAction = AbstractAction.extend(ControlPanelMixin, {
_onValidate: function (event) {
var self = this;
var handle = event.target.handle;
var method = event.name.indexOf('auto_reconciliation') === -1 ? 'validate' : 'autoReconciliation';
var method = 'validate';
this.model[method](handle).then(function (result) {
self.renderer.update({
'valuenow': self.model.valuenow,
@@ -327,7 +325,7 @@ var ManualAction = StatementAction.extend({
//--------------------------------------------------------------------------
/**
* call 'validate' or 'autoReconciliation' model method then destroy the
* call 'validate' model method then destroy the
* reconcilied lines, update the not reconcilied and update the action
* renderer with the new status bar values and notifications then open the
* first available line
@@ -338,7 +336,7 @@ var ManualAction = StatementAction.extend({
_onValidate: function (event) {
var self = this;
var handle = event.target.handle;
var method = event.name.indexOf('auto_reconciliation') === -1 ? 'validate' : 'autoReconciliation';
var method = 'validate';
this.model[method](handle).then(function (result) {
_.each(result.reconciled, function (handle) {
self._getWidget(handle).destroy();
@@ -127,40 +127,22 @@ var StatementModel = BasicModel.extend({
* @returns {Deferred}
*/
addProposition: function (handle, mv_line_id) {
var self = this;
var line = this.getLine(handle);
var prop = _.clone(_.find(line.mv_lines, {'id': mv_line_id}));
this._addProposition(line, prop);
line.limit_override = (line.offset + 1) * this.limitMoveLines;
return $.when(this._computeLine(line), this._performMoveLine(handle));
},
/**
* send information 'account.reconciliation.widget' model to reconciliate
* lines, call rpc to 'auto_reconcile'
* Update the number of validated line
*
* @returns {Deferred<Object>} resolved with an object who contains
* 'handles' key and 'notifications'
*/
autoReconciliation: function () {
var self = this;
var ids = _.pluck(_.filter(this.lines, {'reconciled': false}), 'id');
return this._rpc({
model: 'account.reconciliation.widget',
method: 'auto_reconcile',
args: [ids, self.valuenow],
})
.then(function (result) {
var reconciled_ids = _.difference(ids, result.st_lines_ids);
self.valuenow += reconciled_ids.length;
result.handles = [];
_.each(self.lines, function (line, handle) {
if (reconciled_ids.indexOf(line.id) !== -1) {
line.reconciled = true;
result.handles.push(handle);
}
// Onchange the partner if not already set on the statement line.
if(!line.st_line.partner_id && line.reconciliation_proposition
&& line.reconciliation_proposition.length == 1 && prop.partner_id){
return this.changePartner(handle, {'id': prop.partner_id, 'display_name': prop.partner_name}, true)
.then(function (result) {
return $.when(self._computeLine(line), self._performMoveLine(handle));
});
return result;
});
}
return $.when(this._computeLine(line), this._performMoveLine(handle));
},
/**
* change the filter for the target line and fetch the new matched lines
@@ -236,27 +218,38 @@ var StatementModel = BasicModel.extend({
* change the partner on the line and fetch the new matched lines
*
* @param {string} handle
* @param {bool} preserveMode
* @param {Object} partner
* @param {string} partner.display_name
* @param {number} partner.id
* @returns {Deferred}
*/
changePartner: function (handle, partner) {
changePartner: function (handle, partner, preserveMode) {
var self = this;
var line = this.getLine(handle);
line.st_line.partner_id = partner && partner.id;
line.st_line.partner_name = partner && partner.display_name || '';
return $.when(partner && this._changePartner(handle, partner.id))
.then(function() {
line.reconciliation_proposition = [];
self._computeLine(line);
return self.changeMode(handle, 'match');
if(line.st_line.partner_id){
_.each(line.reconciliation_proposition, function(prop){
if(prop.partner_id != line.st_line.partner_id){
line.reconciliation_proposition = [];
return false;
}
})
}
return self._computeLine(line);
})
.then(function () {
if (line.mode === 'create') {
return self.createProposition(handle);
}
});
if (!preserveMode)
return self.changeMode(handle, 'match');
return false;
})
.then(function () {
return line.mode === 'create' ? self.createProposition(handle) : false;
})
},
/**
* close the statement
@@ -285,7 +278,7 @@ var StatementModel = BasicModel.extend({
var prop = _.filter(line.reconciliation_proposition, '__focus');
var last = prop[prop.length-1];
if (last && !this._isValid(last)) {
return $.Deferred().reject();
return $.Deferred().resolve(false);
}
prop = this._formatQuickCreate(line);
@@ -369,8 +362,10 @@ var StatementModel = BasicModel.extend({
self.valuemax = statement.st_lines_ids.length;
self.context.journal_id = statement.journal_id;
_.each(statement.st_lines_ids, function (id) {
self.lines[_.uniqueId('rline')] = {
var handle = _.uniqueId('rline');
self.lines[handle] = {
id: id,
handle: handle,
reconciled: false,
mode: 'inactive',
mv_lines: [],
@@ -496,6 +491,7 @@ var StatementModel = BasicModel.extend({
removeProposition: function (handle, id) {
var self = this;
var line = this.getLine(handle);
var defs = [];
// new limit = previous limit + 1, the one put back
line.limit_override = (line.offset + 1) * this.limitMoveLines;
var prop = _.find(line.reconciliation_proposition, {'id' : id});
@@ -503,17 +499,21 @@ var StatementModel = BasicModel.extend({
line.reconciliation_proposition = _.filter(line.reconciliation_proposition, function (p) {
return p.id !== prop.id && p.id !== prop.link && p.link !== prop.id && (!p.link || p.link !== prop.link);
});
// No proposition left and then, reset the st_line partner.
if(line.reconciliation_proposition.length == 0 && line.st_line.has_no_partner)
defs.push(self.changePartner(line.handle));
}
line.mode = (id || line.mode !== "create") && isNaN(id) && !this.avoidCreate ? 'create' : 'match';
var def = this._computeLine(line);
defs.push(this._computeLine(line));
if (line.mode === 'create') {
return def.then(function () {
return $.when(defs).then(function () {
return self.createProposition(handle);
});
} else if (line.mode === 'match') {
return $.when(def, self._performMoveLine(handle));
return $.when(defs, self._performMoveLine(handle));
}
return def;
return $.when(defs);
},
searchBalanceAmount: function (handle) {
var line = this.getLine(handle);
@@ -977,7 +977,38 @@ var StatementModel = BasicModel.extend({
self._formatLineProposition(line, line.reconciliation_proposition);
if (!line.reconciliation_proposition.length) {
delete line.reconciliation_proposition;
}else{
// loop state propositions
var debit_props = _.filter(line.reconciliation_proposition, function(p){
return p.amount > 0 && line.st_line.amount;
});
var credit_props = _.filter(line.reconciliation_proposition, function(p){
return p.amount < 0 && line.st_line.amount < 0;
});
var sum_debit_props = debit_props.length > 0 ? debit_props.map(function(p){return p.amount}).reduce(function(p1, p2){return p1 + p2}) : 0;
var sum_credit_props = credit_props.length > 0 ? credit_props.map(function(p){return p.amount}).reduce(function(p1, p2){return p1 + p2}) : 0;
// Want to display the triangle for partial reconciliation or not.
if(line.st_line.amount_currency > 0 && line.st_line.amount_currency < sum_debit_props && debit_props.length == 1)
debit_props[0].display_triangle = true;
else if(line.st_line.amount_currency < 0 && line.st_line.amount_currency > sum_credit_props && credit_props.length == 1)
credit_props[0].display_triangle = true;
}
// No partner set on st_line and all matching amls have the same one: set it on the st_line.
if(!line.st_line.partner_id && line.reconciliation_proposition){
var hasDifferentPartners = function(prop){
return !prop.partner_id || prop.partner_id != line.reconciliation_proposition[0].partner_id;
}
if(!_.any(line.reconciliation_proposition, hasDifferentPartners)){
defs.push(self.changePartner(line.handle, {
'id': line.reconciliation_proposition[0].partner_id,
'display_name': line.reconciliation_proposition[0].partner_name,
}, true));
}
}
defs.push(self._computeLine(line));
});
return $.when.apply($, defs);
@@ -19,7 +19,6 @@ var _t = core._t;
var StatementRenderer = Widget.extend(FieldManagerMixin, {
template: 'reconciliation.statement',
events: {
'click div:first button.o_automatic_reconciliation': '_onAutoReconciliation',
'click div:first h1.statement_name': '_onClickStatementName',
"click *[rel='do_action']": "_onDoAction",
'click button.js_load_more': '_onLoadMore',
@@ -67,13 +66,6 @@ var StatementRenderer = Widget.extend(FieldManagerMixin, {
this.$('h1.statement_name').text(this._initialState.title || _t('No Title'));
this.enterHandler = function (e) {
if ((e.which === 13 || e.which === 10) && (e.ctrlKey || e.metaKey)) {
this.trigger_up('validate_all_balanced');
}
}.bind(this);
$('body').on('keyup', this.enterHandler);
return $.when.apply($, defs);
},
/**
@@ -166,12 +158,6 @@ var StatementRenderer = Widget.extend(FieldManagerMixin, {
// Handlers
//--------------------------------------------------------------------------
/**
* @private
*/
_onAutoReconciliation: function () {
this.trigger_up('auto_reconciliation');
},
/**
* @private
*/
@@ -396,30 +382,17 @@ var LineRenderer = Widget.extend(FieldManagerMixin, {
var targetLineAmount = state.st_line.amount;
_.each(props, function (line) {
var $line = $(qweb.render("reconciliation.line.mv_line", {'line': line, 'state': state}));
var display_triangle = (line.already_paid === false &&
((state.balance.amount_currency < 0 || line.partial_reconcile)
&& line.amount > 0 && state.st_line.amount > 0 && targetLineAmount < line.amount && partialDebitProps <= 1) ||
((state.balance.amount_currency > 0 || line.partial_reconcile)
&& line.amount < 0 && state.st_line.amount < 0 && targetLineAmount > line.amount && partialCreditProps <= 1));
var $line = $(qweb.render("reconciliation.line.mv_line", {'line': line, 'state': state, 'display_triangle': display_triangle}));
if (!isNaN(line.id)) {
$('<span class="line_info_button fa fa-info-circle"/>')
.appendTo($line.find('.cell_info_popover'))
.attr("data-content", qweb.render('reconciliation.line.mv_line.details', {'line': line}));
}
if (line.already_paid === false &&
((state.balance.amount_currency < 0 || line.partial_reconcile)
&& line.amount > 0 && state.st_line.amount > 0 && targetLineAmount < line.amount && partialDebitProps <= 1) ||
((state.balance.amount_currency > 0 || line.partial_reconcile)
&& line.amount < 0 && state.st_line.amount < 0 && targetLineAmount > line.amount && partialCreditProps <= 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(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.");
}
$('<span class="do_partial_reconcile_'+(!line.partial_reconcile)+' line_info_button fa fa-exclamation-triangle"/>')
.prependTo($cell)
.attr("data-content", text);
}
targetLineAmount -= line.amount;
$props.append($line);
@@ -20,7 +20,7 @@ Tour.register('bank_statement_reconciliation', {
method: 'search',
args: [[['name', '=', 'BNK/2014/001']]], // account in l10n_generic_coa
}).then(function(ids) {
var path = "/web#statement_ids=" + ids[0] + "&action=bank_statement_reconciliation_view";
var path = "/web?debug=assets#statement_ids=" + ids[0] + "&action=bank_statement_reconciliation_view";
console.log("'bank_statement_reconciliation' url is: '" + path + "'");
window.location.href = path;
}).fail(function () {
@@ -35,28 +35,23 @@ Tour.register('bank_statement_reconciliation', {
run: function () {},
},
// open a line and reconcile a line proposed by the server
// Reconciliation of 'SAJ/2018/002'
// Select the 'INV/2018/0002' line and click on reconcile.
{
content: "open the last line in match mode to test the reconcile button",
extra_trigger: '.o_reconciliation_line:last .o_reconcile:visible',
trigger: '.o_reconciliation_line:last .accounting_view thead .cell_label:contains("/002")',
trigger: '.toggle_match:last',
},
{
content: "deselect the proposed line",
extra_trigger: '.o_reconciliation_line:last[data-mode="match"]',
trigger: '.o_reconciliation_line:last .accounting_view .cell_label:contains("/0002")'
},
{
content: "re-select the line",
extra_trigger: '.o_reconciliation_line:last .o_no_valid:visible', // the user can't validate the line and display the write-off line
trigger: '.o_reconciliation_line:last .match .cell_label:contains("INV"):contains("/0002")'
content: "select the 'INV/2018/0002' line",
trigger: '.o_reconciliation_line:last .match .cell_label:contains("INV/2018/0002")'
},
{
content: "reconcile the line",
trigger: '.o_reconciliation_line:last .o_reconcile:visible',
},
// Reconciliation of 'First 2000 € of SAJ/2014/0001'
// Make a partial reconciliation
{
@@ -66,7 +61,7 @@ Tour.register('bank_statement_reconciliation', {
},
{
content: "select a line with with a higher amount",
trigger: '.o_reconciliation_line:last .match .cell_right:contains($ 4,610.00)'
trigger: '.o_reconciliation_line:last .match .cell_label:contains("INV/2018/0001")'
},
{
content: "click on partial reconcile",
@@ -77,6 +72,7 @@ Tour.register('bank_statement_reconciliation', {
trigger: '.o_reconciliation_line:last .o_reconcile:visible',
},
// Reconciliation of 'Prepayment'
// Test changing the partner
{
@@ -91,7 +87,6 @@ Tour.register('bank_statement_reconciliation', {
},
{
content: "use filter",
extra_trigger: '.o_reconciliation_line:nth-child(2) .match:not(:has(tr:eq(2))) tr:eq(1)',
trigger: '.o_reconciliation_line:nth-child(2) .match .match_controls .filter',
run: 'text 4610'
},
@@ -26,7 +26,6 @@
</div>
<div aria-valuemin="0" t-att-aria-valuenow="widget._initialState.valuenow" class="progress-bar" role="progressbar" style="width: 0%;" t-att-aria-valuemax="widget._initialState.valuemax"></div>
</div>
<button class="btn btn-secondary o_automatic_reconciliation float-right" title="Let odoo try to reconcile entries for the user">Automatic reconciliation</button>
<div class="notification_area"/>
</div>
<div class="o_reconciliation_lines"/>
@@ -45,7 +44,6 @@
<t t-jquery="div:first" t-operation="attributes">
<attribute name="class">o_manual_statement</attribute>
</t>
<t t-jquery="h1:last, .progress, .o_automatic_reconciliation" t-operation="replace"></t>
<t t-jquery=".o_view_noreconciliation p" t-operation="replace"></t>
<t t-jquery=".o_view_noreconciliation" t-operation="append">
<p><b>Good Job!</b> There is nothing to reconcile.</p>
@@ -215,6 +213,20 @@
</div>
<t t-name="reconciliation.line.mv_line.triangle">
<t t-if="display_triangle || 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 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>
</t>
<t t-name="reconciliation.line.mv_line">
<tr t-if="line.display !== false" t-attf-class="mv_line #{line.already_paid ? ' already_reconciled' : ''} #{line.__invalid ? 'invalid' : ''} #{line.is_tax ? 'is_tax' : ''}" t-att-data-line-id="line.id" t-att-data-selected="selected">
<td class="cell_action"><span class="fa fa-add-remove" role="img" aria-label="Set" title="Set"/></td>
@@ -230,8 +242,24 @@
<t t-if="line.ref &amp;&amp; line.ref.length"> : </t>
<t t-esc="line.ref"/>
</td>
<td class="cell_left"><t t-if="line.amount &lt; 0"><span role="img" 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-att-aria-label="line.amount_currency_str" t-att-title="line.amount_currency_str"/><t t-raw="line.amount_str"/></t></td>
<td class="cell_right"><t t-if="line.amount &gt; 0"><span role="img" 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-att-aria-label="line.amount_currency_str" t-att-title="line.amount_currency_str"/><t t-raw="line.amount_str"/></t></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>
</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>
</td>
<td class="cell_info_popover"></td>
</tr>
</t>
@@ -666,23 +666,22 @@ QUnit.module('account', {
});
clientAction.appendTo($('#qunit-fixture'));
var widget = clientAction.widgets[0];
assert.strictEqual(widget.$('.mv_line').length, 2, "should display 2 account move lines");
assert.strictEqual(widget.$('.mv_line').text().replace(/[\n\r\s]+/g, ' '),
" 101200 2017-02-07 INV/2017/0002 $ 650.00 101200 2017-02-07 INV/2017/0003 $ 525.00 ",
"should display 4 account move lines who contains the account_code, due_date, label and the credit");
assert.strictEqual(widget.$('.mv_line .cell_right:not(:empty)').length, 2, "should display only the credit account move lines (hide the debit)");
assert.strictEqual(widget.$('.mv_line .cell_right:contains(".")').length, 2, "should display only the credit account move lines (hide the debit)");
clientAction.widgets[1].$('.accounting_view thead td:first').trigger('click');
assert.strictEqual(clientAction.widgets[1].$('.mv_line').length, 5, "should display 5 account move lines");
assert.strictEqual(clientAction.widgets[1].$('.mv_line .cell_right:not(:empty)').length, 4, "should display only the credit account move lines (hide the debit)");
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.already_reconciled').length, 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 ",
"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:not(:empty)').length, 1, "should display only 1 debit account move lines");
assert.strictEqual(clientAction.widgets[1].$('.mv_line .cell_left:contains(".")').length, 1, "should display only 1 debit account move lines");
// load more
assert.ok(clientAction.widgets[1].$('.match div.load-more a:visible').length, "should display the 'load more' button");
@@ -833,10 +832,11 @@ QUnit.module('account', {
mockRPC: function (route, args) {
console.log(args.method);
if (args.method === 'process_bank_statement_line') {
var lines = args.args['1'];
assert.deepEqual(args.args, [
[6],
[{
partner_id: false,
partner_id: lines.length == 1 ? lines[0].partner_id : false,
counterpart_aml_dicts:[],
payment_aml_ids: [392],
new_aml_dicts: [],
@@ -869,7 +869,12 @@ QUnit.module('account', {
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.$('button.btn-primary:visible').length, 0, "should not display the reconcile button");
// 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.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 .line_info_button').trigger('click');
assert.strictEqual(widget.$('.accounting_view .cell_left .line_info_button').length, 1, "should display a partial reconciliation alert");
@@ -1054,7 +1059,7 @@ QUnit.module('account', {
widget.$('.create .create_amount input').val('1100.00').trigger('input');
assert.strictEqual(widget.$('.accounting_view tbody .cell_right').text(), "$ 1100.00", "should display the value 1100.00 in right column");
assert.strictEqual(widget.$('.accounting_view tbody .cell_right').text().trim().replace(/[\n\r\s\u00a0]+/g, ' '), "$ 1100.00", "should display the value 1100.00 in right column");
assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "$ 75.00", "should display 'Open Balance' line because the rest to reconcile is 75.00");
assert.strictEqual(widget.$('.accounting_view tbody tr').length, 1, "should have ever only the created reconcile line");
assert.strictEqual(widget.$('.accounting_view tbody tr').text().replace(/[\n\r\s$,]+/g, ' '), " 101200 SAJ/2014/002 and SAJ/2014/003 1100.00 ",
@@ -1067,7 +1072,7 @@ QUnit.module('account', {
$('.ui-autocomplete .ui-menu-item a:contains(101000 Current Assets)').trigger('mouseenter').trigger('click');
widget.$('.create .create_label input').val('test0').trigger('input');
assert.strictEqual(widget.$('.accounting_view tbody .cell_left:last').text(), "$ 100.00", "should display the value 100.00 in left column");
assert.strictEqual(widget.$('.accounting_view tbody .cell_left:last').text().trim().replace(/[\n\r\s\u00a0]+/g, ' '), "$ 100.00", "should display the value 100.00 in left column");
assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Open balance", "should display 'Open Balance'");
assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "$ 175.00", "should display 'Open Balance' line because the rest to reconcile is 175.00");
assert.strictEqual(widget.$('.accounting_view tbody tr').length, 2, "should have 2 created reconcile lines");
@@ -1084,7 +1089,7 @@ QUnit.module('account', {
$('.ui-autocomplete .ui-menu-item a:contains(101000 Current Assets)').trigger('mouseenter').trigger('click');
widget.$('.create .create_label input').val('test1').trigger('input');
assert.strictEqual(widget.$('.accounting_view tbody .cell_right:last').text(), "$ 200.00", "should display the value 200.00 in left column");
assert.strictEqual(widget.$('.accounting_view tbody .cell_right:last').text().trim().replace(/[\n\r\s\u00a0]+/g, ' '), "$ 200.00", "should display the value 200.00 in left column");
assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Open balance", "should display 'Open balance'");
assert.strictEqual(widget.$('.accounting_view tfoot .cell_left').text(), "$ 25.00", "should display 'Open balance' with 25.00 in left column");
assert.strictEqual(widget.$('.accounting_view tbody tr').length, 3, "should have 3 created reconcile lines");
@@ -1174,7 +1179,7 @@ QUnit.module('account', {
widget.$('.create .create_label input').val('test1').trigger('input');
widget.$('.create .create_amount input').val('1100').trigger('input');
assert.strictEqual(widget.$('.accounting_view tbody .cell_right:last').text(), "$\u00a01100.00", "should display the value 1100.00 in left column");
assert.strictEqual(widget.$('.accounting_view tbody .cell_right:last').text().trim().replace(/[\n\r\s\u00a0]+/g, ' '), "$ 1100.00", "should display the value 1100.00 in left column");
assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Open balance", "should display 'Open Balance'");
assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "$\u00a075.00", "should display 'Open Balance' with 75.00 in right column");
assert.strictEqual(widget.$('.accounting_view tbody tr').length, 1, "should have 1 created reconcile lines");
@@ -1182,7 +1187,7 @@ QUnit.module('account', {
widget.$('.create .create_tax_id input').trigger('click');
$('.ui-autocomplete .ui-menu-item a:contains(10.00%)').trigger('mouseenter').trigger('click');
assert.strictEqual(widget.$('.accounting_view tbody .cell_right').text().replace('$_', ''), "$\u00a01000.00$\u00a0100.00", "should have 2 created reconcile lines with right column values");
assert.strictEqual(widget.$('.accounting_view tbody .cell_right').text().trim().replace(/[\n\r\s\u00a0]+/g, ' '), "$ 1000.00 $ 100.00", "should have 2 created reconcile lines with right column values");
assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Open balance", "should display 'Open Balance'");
assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "$\u00a075.00", "should display 'Open Balance' with 75.00 in right column");
assert.strictEqual(widget.$('.accounting_view tfoot .cell_left').text(), "", "should display 'Open Balance' without any value in left column");
@@ -1191,7 +1196,7 @@ QUnit.module('account', {
widget.$('.create .create_tax_id input').trigger('click');
$('.ui-autocomplete .ui-menu-item a:contains(20.00%)').trigger('mouseenter').trigger('click');
assert.strictEqual(widget.$('.accounting_view tbody .cell_right').text().replace('$_', ''), "$\u00a01100.00$\u00a0220.00", "should have 2 created reconcile lines with right column values");
assert.strictEqual(widget.$('.accounting_view tbody .cell_right').text().trim().replace(/[\n\r\s\u00a0]+/g, ' '), "$ 1100.00 $ 220.00", "should have 2 created reconcile lines with right column values");
assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Open balance", "should display 'Open balance'");
assert.strictEqual(widget.$('.accounting_view tfoot .cell_left').text(), "$\u00a0145.00", "should display 'Open balance' with 145.00 in right column");
assert.strictEqual(widget.$('.accounting_view tbody tr').length, 2, "should have 2 created reconcile lines");
@@ -1215,7 +1220,7 @@ QUnit.module('account', {
widget.$('.create .quick_add button:contains(ATOS)').trigger('click');
assert.strictEqual(widget.$('.accounting_view tbody .cell_label, .accounting_view tbody .cell_right').text().replace(/[\n\r\s$,]+/g, ' '),
" ATOS Banque 1145.62 Tax 20.00% 229.12 ATOS Frais 26.71 Tax 10.00% include 2.67", "should display 4 lines");
" ATOS Banque 1145.62 Tax 20.00% 229.12 ATOS Frais 26.71 Tax 10.00% include 2.67 ", "should display 4 lines");
assert.strictEqual(widget.$('.accounting_view tfoot .cell_label, .accounting_view tfoot .cell_left').text().replace(/[\n\r\s$,]+/g, ' '),
"Open balance229.12", "should display the 'Open balance' line with value in left column");
@@ -1241,37 +1246,6 @@ QUnit.module('account', {
clientAction.destroy();
});
QUnit.test('Reconciliation auto reconciliation', function (assert) {
assert.expect(6);
var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options);
testUtils.addMockEnvironment(clientAction, {
'data': this.params.data,
});
clientAction.appendTo($('#qunit-fixture'));
clientAction.$('button.o_automatic_reconciliation').trigger('click');
assert.strictEqual(clientAction.$('.progress .valuenow').text(), "1", "should reconciled one line");
assert.strictEqual(clientAction.$('.progress .valuemax').text(), "4", "should have maximum 4 lines to reconciled");
assert.strictEqual(clientAction.$('thead .cell_label').text().replace(/[\n\r\s$,]+/g, ' '),
"SAJ/2014/002 and SAJ/2014/003 Bank fees First 2000 € of SAJ/2014/001 ", "should rest 3 lines to reconciled");
assert.ok(clientAction.$('.notification_area .notification').length, "should display the notification");
var do_action = false;
clientAction.on('do_action', clientAction, function (data) {
do_action = data.data.action;
});
clientAction.$('.notification_area a.fa-external-link').trigger('click');
assert.deepEqual(_.pick(do_action, "res_model", "domain", "type"),
{"res_model":"account.move", "domain":[['id',"in",[143]]], "type":"ir.actions.act_window"},
"should try to open the record form view");
clientAction.$('.notification_area .close').trigger('click');
assert.notOk(clientAction.$('.notification_area .notification').length, "should close the notification");
clientAction.destroy();
});
QUnit.test('Reconciliation manual', function (assert) {
assert.expect(13);
@@ -1296,7 +1270,7 @@ QUnit.module('account', {
" Agrolait 101200 ",
"should display the partner and the account code as title");
assert.strictEqual(clientAction.$('.o_reconciliation_line:first .match tr:first .cell_right').text(),
assert.strictEqual(clientAction.$('.o_reconciliation_line:first .match tr:first .cell_right').text().trim().replace(/[\n\r\s\u00a0]+/g, ' '),
"$ 11,000.00", "sould display the line in $");
assert.strictEqual(clientAction.$('.o_reconciliation_line:first .match tr:first .cell_right .o_multi_currency').data('content'),
"10,222.00 €", "sould display the monetary information in €");
@@ -12,7 +12,7 @@ class TestBankStatementReconciliation(AccountingTestCase):
self.bs_model = self.env['account.bank.statement']
self.bsl_model = self.env['account.bank.statement.line']
self.reconciliation_widget = self.env['account.reconciliation.widget']
self.partner_agrolait = self.env.ref("base.res_partner_2")
self.partner = self.env['res.partner'].create({'name': 'test'})
def test_reconciliation_proposition(self):
rcv_mv_line = self.create_invoice(100)
@@ -53,7 +53,7 @@ class TestBankStatementReconciliation(AccountingTestCase):
def create_invoice(self, amount):
""" Return the move line that gets to be reconciled (the one in the receivable account) """
vals = {'partner_id': self.partner_agrolait.id,
vals = {'partner_id': self.partner.id,
'type': 'out_invoice',
'name': '-',
'currency_id': self.env.user.company_id.currency_id.id,
@@ -89,7 +89,7 @@ class TestBankStatementReconciliation(AccountingTestCase):
bank_stmt_line = self.bsl_model.create({
'name': '_',
'statement_id': bank_stmt.id,
'partner_id': self.partner_agrolait.id,
'partner_id': self.partner.id,
'amount': st_line_amount,
})