[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:
@@ -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).
|
||||
|
||||
@@ -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 && line.ref.length"> : </t>
|
||||
<t t-esc="line.ref"/>
|
||||
</td>
|
||||
<td class="cell_left"><t t-if="line.amount < 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 > 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 < 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 > 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,
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user