[FIX] account: several fixes on the new bank statement reconciliation widget

This commit is contained in:
qdp-odoo 2014-09-04 11:32:16 +02:00
parent e833066b63
commit f4e350ca36
22 changed files with 1124 additions and 638 deletions

View File

@ -55,6 +55,19 @@ def check_cycle(self, cr, uid, ids, context=None):
level -= 1 level -= 1
return True return True
class res_company(osv.osv):
_inherit = "res.company"
_columns = {
'income_currency_exchange_account_id': fields.many2one(
'account.account',
string="Gain Exchange Rate Account",
domain="[('type', '=', 'other')]",),
'expense_currency_exchange_account_id': fields.many2one(
'account.account',
string="Loss Exchange Rate Account",
domain="[('type', '=', 'other')]",),
}
class account_payment_term(osv.osv): class account_payment_term(osv.osv):
_name = "account.payment.term" _name = "account.payment.term"
_description = "Payment Term" _description = "Payment Term"

View File

@ -405,31 +405,9 @@ class account_bank_statement(osv.osv):
'context':ctx, 'context':ctx,
} }
def number_of_lines_reconciled(self, cr, uid, id, context=None): def number_of_lines_reconciled(self, cr, uid, ids, context=None):
bsl_obj = self.pool.get('account.bank.statement.line') bsl_obj = self.pool.get('account.bank.statement.line')
return bsl_obj.search_count(cr, uid, [('statement_id', '=', id), ('journal_entry_id', '!=', False)], context=context) return bsl_obj.search_count(cr, uid, [('statement_id', 'in', ids), ('journal_entry_id', '!=', False)], context=context)
def get_format_currency_js_function(self, cr, uid, id, context=None):
""" Returns a string that can be used to instanciate a javascript function.
That function formats a number according to the statement line's currency or the statement currency"""
company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id
st = id and self.browse(cr, uid, id, context=context)
if not st:
return
statement_currency = st.journal_id.currency or company_currency
digits = 2 # TODO : from currency_obj
function = ""
done_currencies = []
for st_line in st.line_ids:
st_line_currency = st_line.currency_id or statement_currency
if st_line_currency.id not in done_currencies:
if st_line_currency.position == 'after':
return_str = "return amount.toFixed(" + str(digits) + ") + ' " + st_line_currency.symbol + "';"
else:
return_str = "return '" + st_line_currency.symbol + " ' + amount.toFixed(" + str(digits) + ");"
function += "if (currency_id === " + str(st_line_currency.id) + "){ " + return_str + " }"
done_currencies.append(st_line_currency.id)
return function
def link_bank_to_partner(self, cr, uid, ids, context=None): def link_bank_to_partner(self, cr, uid, ids, context=None):
for statement in self.browse(cr, uid, ids, context=context): for statement in self.browse(cr, uid, ids, context=context):
@ -465,98 +443,102 @@ class account_bank_statement_line(osv.osv):
account_move_obj.button_cancel(cr, uid, move_ids, context=context) account_move_obj.button_cancel(cr, uid, move_ids, context=context)
account_move_obj.unlink(cr, uid, move_ids, context) account_move_obj.unlink(cr, uid, move_ids, context)
def get_data_for_reconciliations(self, cr, uid, ids, context=None): def get_data_for_reconciliations(self, cr, uid, ids, excluded_ids=None, search_reconciliation_proposition=True, context=None):
""" Used to instanciate a batch of reconciliations in a single request """ """ Returns the data required to display a reconciliation, for each statement line id in ids """
# Build a list of reconciliations data
ret = [] ret = []
statement_line_done = {} if excluded_ids is None:
mv_line_ids_selected = [] excluded_ids = []
for st_line in self.browse(cr, uid, ids, context=context):
# look for structured communication first
exact_match_id = self.search_structured_com(cr, uid, st_line, context=context)
if exact_match_id:
reconciliation_data = {
'st_line': self.get_statement_line_for_reconciliation(cr, uid, st_line.id, context),
'reconciliation_proposition': self.make_counter_part_lines(cr, uid, st_line, [exact_match_id], context=context)
}
for mv_line in reconciliation_data['reconciliation_proposition']:
mv_line_ids_selected.append(mv_line['id'])
statement_line_done[st_line.id] = reconciliation_data
for st_line_id in ids: for st_line in self.browse(cr, uid, ids, context=context):
if statement_line_done.get(st_line_id): reconciliation_data = {}
ret.append(statement_line_done.get(st_line_id)) if search_reconciliation_proposition:
reconciliation_proposition = self.get_reconciliation_proposition(cr, uid, st_line, excluded_ids=excluded_ids, context=context)
for mv_line in reconciliation_proposition:
excluded_ids.append(mv_line['id'])
reconciliation_data['reconciliation_proposition'] = reconciliation_proposition
else: else:
reconciliation_data = { reconciliation_data['reconciliation_proposition'] = []
'st_line': self.get_statement_line_for_reconciliation(cr, uid, st_line_id, context), st_line = self.get_statement_line_for_reconciliation(cr, uid, st_line, context=context)
'reconciliation_proposition': self.get_reconciliation_proposition(cr, uid, st_line_id, mv_line_ids_selected, context) reconciliation_data['st_line'] = st_line
}
for mv_line in reconciliation_data['reconciliation_proposition']:
mv_line_ids_selected.append(mv_line['id'])
ret.append(reconciliation_data) ret.append(reconciliation_data)
# Check if, now that 'candidate' move lines were selected, there are moves left for statement lines
#for reconciliation_data in ret:
# if not reconciliation_data['st_line']['has_no_partner']:
# st_line = self.browse(cr, uid, reconciliation_data['st_line']['id'], context=context)
# if not self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=mv_line_ids_selected, count=True, context=context):
# reconciliation_data['st_line']['no_match'] = True
return ret return ret
def get_statement_line_for_reconciliation(self, cr, uid, id, context=None): def get_statement_line_for_reconciliation(self, cr, uid, st_line, context=None):
""" Returns the data required by the bank statement reconciliation use case """ """ Returns the data required by the bank statement reconciliation widget to display a statement line """
line = self.browse(cr, uid, id, context=context) if context is None:
statement_currency = line.journal_id.currency or line.journal_id.company_id.currency_id context = {}
amount = line.amount statement_currency = st_line.journal_id.currency or st_line.journal_id.company_id.currency_id
rml_parser = report_sxw.rml_parse(cr, uid, 'statement_line_widget', context=context) rml_parser = report_sxw.rml_parse(cr, uid, 'reconciliation_widget_asl', context=context)
amount_str = line.amount > 0 and line.amount or -line.amount
amount_str = rml_parser.formatLang(amount_str, currency_obj=statement_currency) if st_line.amount_currency and st_line.currency_id:
amount = st_line.amount_currency
amount_currency = st_line.amount
amount_currency_str = amount_currency > 0 and amount_currency or -amount_currency
amount_currency_str = rml_parser.formatLang(amount_currency_str, currency_obj=statement_currency)
else:
amount = st_line.amount
amount_currency_str = "" amount_currency_str = ""
if line.amount_currency and line.currency_id: amount_str = amount > 0 and amount or -amount
amount_currency_str = amount_str amount_str = rml_parser.formatLang(amount_str, currency_obj=st_line.currency_id or statement_currency)
amount_str = rml_parser.formatLang(line.amount_currency, currency_obj=line.currency_id)
amount = line.amount_currency
data = { data = {
'id': line.id, 'id': st_line.id,
'ref': line.ref, 'ref': st_line.ref,
'note': line.note or "", 'note': st_line.note or "",
'name': line.name, 'name': st_line.name,
'date': line.date, 'date': st_line.date,
'amount': amount, 'amount': amount,
'amount_str': amount_str, 'amount_str': amount_str, # Amount in the statement line currency
'currency_id': line.currency_id.id or statement_currency.id, 'currency_id': st_line.currency_id.id or statement_currency.id,
'no_match': self.get_move_lines_counterparts(cr, uid, line, count=True, context=context) == 0, 'partner_id': st_line.partner_id.id,
'partner_id': line.partner_id.id, 'statement_id': st_line.statement_id.id,
'statement_id': line.statement_id.id, 'account_code': st_line.journal_id.default_debit_account_id.code,
'account_code': line.journal_id.default_debit_account_id.code, 'account_name': st_line.journal_id.default_debit_account_id.name,
'account_name': line.journal_id.default_debit_account_id.name, 'partner_name': st_line.partner_id.name,
'partner_name': line.partner_id and line.partner_id.name or line.partner_name, 'amount_currency_str': amount_currency_str, # Amount in the statement currency
'amount_currency_str': amount_currency_str, 'has_no_partner': not st_line.partner_id.id,
'has_no_partner': not line.partner_id.id,
} }
if line.partner_id.id: if st_line.partner_id.id:
data['open_balance_account_id'] = line.partner_id.property_account_payable.id
if amount > 0: if amount > 0:
data['open_balance_account_id'] = line.partner_id.property_account_receivable.id data['open_balance_account_id'] = st_line.partner_id.property_account_receivable.id
else:
data['open_balance_account_id'] = st_line.partner_id.property_account_payable.id
return data return data
def search_structured_com(self, cr, uid, st_line, context=None): def get_reconciliation_proposition(self, cr, uid, st_line, excluded_ids=None, context=None):
domain = [('ref', '=', st_line.name.replace('/', ''))]
if st_line.partner_id:
domain += [('partner_id', '=', st_line.partner_id.id)]
ids = self.pool.get('account.move.line').search(cr, uid, domain, limit=1, context=context)
return ids and ids[0] or False
def get_reconciliation_proposition(self, cr, uid, id, excluded_ids=[], context=None):
""" Returns move lines that constitute the best guess to reconcile a statement line. """ """ Returns move lines that constitute the best guess to reconcile a statement line. """
st_line = self.browse(cr, uid, id, context=context) if excluded_ids is None:
excluded_ids = []
mv_line_pool = self.pool.get('account.move.line')
# Look for structured communication
if st_line.name:
structured_com_match_domain = [('ref', '=', st_line.name),('reconcile_id', '=', False),('state', '=', 'valid'),('account_id.reconcile', '=', True),('id', 'not in', excluded_ids)]
match_id = mv_line_pool.search(cr, uid, structured_com_match_domain, offset=0, limit=1, context=context)
if match_id:
mv_line_br = mv_line_pool.browse(cr, uid, match_id, context=context)
target_currency = st_line.currency_id or st_line.journal_id.currency or st_line.journal_id.company_id.currency_id
mv_line = mv_line_pool.prepare_move_lines_for_reconciliation_widget(cr, uid, mv_line_br, target_currency=target_currency, target_date=st_line.date, context=context)[0]
mv_line['has_no_partner'] = not bool(st_line.partner_id.id)
# If the structured communication matches a move line that is associated with a partner, we can safely associate the statement line with the partner
if (mv_line['partner_id']):
self.write(cr, uid, st_line.id, {'partner_id': mv_line['partner_id']}, context=context)
mv_line['has_no_partner'] = False
return [mv_line]
# If there is no identified partner or structured communication, don't look further
if not st_line.partner_id.id:
return []
# Look for a move line whose amount matches the statement line's amount
company_currency = st_line.journal_id.company_id.currency_id.id company_currency = st_line.journal_id.company_id.currency_id.id
statement_currency = st_line.journal_id.currency.id or company_currency statement_currency = st_line.journal_id.currency.id or company_currency
# either use the unsigned debit/credit fields or the signed amount_currency field
sign = 1 sign = 1
if statement_currency == company_currency: if statement_currency == company_currency:
amount_field = 'credit' amount_field = 'credit'
sign = -1
if st_line.amount > 0: if st_line.amount > 0:
amount_field = 'debit' amount_field = 'debit'
else: else:
@ -564,124 +546,74 @@ class account_bank_statement_line(osv.osv):
if st_line.amount < 0: if st_line.amount < 0:
sign = -1 sign = -1
#we don't propose anything if there is no partner detected match_id = self.get_move_lines_for_reconciliation(cr, uid, st_line, excluded_ids=excluded_ids, offset=0, limit=1, additional_domain=[(amount_field, '=', (sign * st_line.amount))])
if not st_line.partner_id.id: if match_id:
return [match_id[0]]
return [] return []
# look for exact match
exact_match_id = self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=excluded_ids, additional_domain=[(amount_field, '=', (sign * st_line.amount))])
if exact_match_id:
return exact_match_id
# select oldest move lines def get_move_lines_for_reconciliation_by_statement_line_id(self, cr, uid, st_line_id, excluded_ids=None, str=False, offset=0, limit=None, count=False, additional_domain=None, context=None):
if sign == -1: """ Bridge between the web client reconciliation widget and get_move_lines_for_reconciliation (which expects a browse record) """
mv_lines = self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=excluded_ids, additional_domain=[(amount_field, '<', 0)]) if excluded_ids is None:
else: excluded_ids = []
mv_lines = self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=excluded_ids, additional_domain=[(amount_field, '>', 0)]) if additional_domain is None:
ret = [] additional_domain = []
total = 0
# get_move_lines_counterparts inverts debit and credit
amount_field = 'debit' if amount_field == 'credit' else 'credit'
for line in mv_lines:
if total + line[amount_field] <= abs(st_line.amount):
ret.append(line)
total += line[amount_field]
if total >= abs(st_line.amount):
break
return ret
def get_move_lines_counterparts_id(self, cr, uid, st_line_id, excluded_ids=[], additional_domain=[], count=False, context=None):
st_line = self.browse(cr, uid, st_line_id, context=context) st_line = self.browse(cr, uid, st_line_id, context=context)
return self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids, additional_domain, count, context=context) return self.get_move_lines_for_reconciliation(cr, uid, st_line, excluded_ids, str, offset, limit, count, additional_domain, context=context)
def get_move_lines_counterparts(self, cr, uid, st_line, excluded_ids=[], additional_domain=[], count=False, context=None): def get_move_lines_for_reconciliation(self, cr, uid, st_line, excluded_ids=None, str=False, offset=0, limit=None, count=False, additional_domain=None, context=None):
""" Find the move lines that could be used to reconcile a statement line and returns the counterpart that could be created to reconcile them """ Find the move lines that could be used to reconcile a statement line. If count is true, only returns the count.
If count is true, only returns the count.
:param st_line: the browse record of the statement line :param st_line: the browse record of the statement line
:param integers list excluded_ids: ids of move lines that should not be fetched :param integers list excluded_ids: ids of move lines that should not be fetched
:param string filter_str: string to filter lines
:param integer offset: offset of the request
:param integer limit: number of lines to fetch
:param boolean count: just return the number of records :param boolean count: just return the number of records
:param tuples list domain: additional domain restrictions :param tuples list additional_domain: additional domain restrictions
""" """
if excluded_ids is None:
excluded_ids = []
if additional_domain is None:
additional_domain = []
mv_line_pool = self.pool.get('account.move.line') mv_line_pool = self.pool.get('account.move.line')
domain = additional_domain + [('reconcile_id', '=', False), ('state', '=', 'valid')] # Make domain
domain = additional_domain + [('reconcile_id', '=', False),('state', '=', 'valid')]
if st_line.partner_id.id: if st_line.partner_id.id:
domain += [('partner_id', '=', st_line.partner_id.id), domain += [('partner_id', '=', st_line.partner_id.id),
'|', ('account_id.type', '=', 'receivable'), '|', ('account_id.type', '=', 'receivable'),
('account_id.type', '=', 'payable')] ('account_id.type', '=', 'payable')]
else: else:
domain += [('account_id.reconcile', '=', True), ('account_id.type', '=', 'other')] domain += [('account_id.reconcile', '=', True), ('account_id.type', '=', 'other')]
if str:
domain += [('partner_id.name', 'ilike', str)]
if excluded_ids: if excluded_ids:
domain.append(('id', 'not in', excluded_ids)) domain.append(('id', 'not in', excluded_ids))
line_ids = mv_line_pool.search(cr, uid, domain, order="date_maturity asc, id asc", context=context) if str:
return self.make_counter_part_lines(cr, uid, st_line, line_ids, count=count, context=context) domain += ['|', ('move_id.name', 'ilike', str), ('move_id.ref', 'ilike', str)]
def make_counter_part_lines(self, cr, uid, st_line, line_ids, count=False, context=None): # Get move lines
if context is None: line_ids = mv_line_pool.search(cr, uid, domain, offset=offset, limit=limit, order="date_maturity asc, id asc", context=context)
context = {} lines = mv_line_pool.browse(cr, uid, line_ids, context=context)
mv_line_pool = self.pool.get('account.move.line')
currency_obj = self.pool.get('res.currency') # Either return number of lines
company_currency = st_line.journal_id.company_id.currency_id
statement_currency = st_line.journal_id.currency or company_currency
rml_parser = report_sxw.rml_parse(cr, uid, 'statement_line_counterpart_widget', context=context)
#partially reconciled lines can be displayed only once
reconcile_partial_ids = []
if count: if count:
nb_lines = 0 nb_lines = 0
for line in mv_line_pool.browse(cr, uid, line_ids, context=context): reconcile_partial_ids = [] # for a partial reconciliation, take only one line
for line in lines:
if line.reconcile_partial_id and line.reconcile_partial_id.id in reconcile_partial_ids: if line.reconcile_partial_id and line.reconcile_partial_id.id in reconcile_partial_ids:
continue continue
nb_lines += 1 nb_lines += 1
if line.reconcile_partial_id: if line.reconcile_partial_id:
reconcile_partial_ids.append(line.reconcile_partial_id.id) reconcile_partial_ids.append(line.reconcile_partial_id.id)
return nb_lines return nb_lines
# Or return list of dicts representing the formatted move lines
else: else:
ret = [] target_currency = st_line.currency_id or st_line.journal_id.currency or st_line.journal_id.company_id.currency_id
for line in mv_line_pool.browse(cr, uid, line_ids, context=context): mv_lines = mv_line_pool.prepare_move_lines_for_reconciliation_widget(cr, uid, lines, target_currency=target_currency, target_date=st_line.date, context=context)
if line.reconcile_partial_id and line.reconcile_partial_id.id in reconcile_partial_ids: has_no_partner = not bool(st_line.partner_id.id)
continue for line in mv_lines:
amount_currency_str = "" line['has_no_partner'] = has_no_partner
if line.currency_id and line.amount_currency: return mv_lines
amount_currency_str = rml_parser.formatLang(line.amount_currency, currency_obj=line.currency_id)
ret_line = {
'id': line.id,
'name': line.move_id.name,
'ref': line.move_id.ref,
'account_code': line.account_id.code,
'account_name': line.account_id.name,
'account_type': line.account_id.type,
'date_maturity': line.date_maturity,
'date': line.date,
'period_name': line.period_id.name,
'journal_name': line.journal_id.name,
'amount_currency_str': amount_currency_str,
'partner_id': line.partner_id.id,
'partner_name': line.partner_id.name,
'has_no_partner': not bool(st_line.partner_id.id),
}
st_line_currency = st_line.currency_id or statement_currency
line_currency = line.currency_id or company_currency
if line_currency == st_line_currency and line_currency != company_currency:
ret_line['debit'] = line.credit > 0 and -line.amount_residual_currency or 0.0
ret_line['credit'] = line.debit > 0 and line.amount_residual_currency or 0.0
ret_line['amount_currency_str'] = rml_parser.formatLang(line.amount_residual, currency_obj=company_currency)
else:
ret_line['debit'] = line.credit > 0 and -line.amount_residual or 0.0
ret_line['credit'] = line.debit > 0 and line.amount_residual or 0.0
if st_line_currency != company_currency:
ctx = context.copy()
ctx.update({'date': st_line.date})
ret_line['debit'] = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, ret_line['debit'], context=ctx)
ret_line['credit'] = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, ret_line['credit'], context=ctx)
ret_line['debit_str'] = rml_parser.formatLang(ret_line['debit'], currency_obj=st_line_currency)
ret_line['credit_str'] = rml_parser.formatLang(ret_line['credit'], currency_obj=st_line_currency)
ret.append(ret_line)
if line.reconcile_partial_id:
reconcile_partial_ids.append(line.reconcile_partial_id.id)
return ret
def get_currency_rate_line(self, cr, uid, st_line, currency_diff, move_id, context=None): def get_currency_rate_line(self, cr, uid, st_line, currency_diff, move_id, context=None):
if currency_diff < 0: if currency_diff < 0:
@ -769,20 +701,21 @@ class account_bank_statement_line(osv.osv):
mv_line = aml_obj.browse(cr, uid, mv_line_dict['counterpart_move_line_id'], context=context) mv_line = aml_obj.browse(cr, uid, mv_line_dict['counterpart_move_line_id'], context=context)
mv_line_dict['account_id'] = mv_line.account_id.id mv_line_dict['account_id'] = mv_line.account_id.id
if st_line_currency.id != company_currency.id: if st_line_currency.id != company_currency.id:
ctx = context.copy()
ctx['date'] = st_line.date
mv_line_dict['amount_currency'] = mv_line_dict['debit'] - mv_line_dict['credit'] mv_line_dict['amount_currency'] = mv_line_dict['debit'] - mv_line_dict['credit']
mv_line_dict['currency_id'] = st_line_currency.id mv_line_dict['currency_id'] = st_line_currency.id
if st_line.currency_id and statement_currency.id == company_currency.id and st_line_currency_rate: if st_line.currency_id and statement_currency.id == company_currency.id and st_line_currency_rate:
debit_at_current_rate = self.pool.get('res.currency').round(cr, uid, company_currency, mv_line_dict['debit'] / st_line_currency_rate) debit_at_current_rate = self.pool.get('res.currency').round(cr, uid, company_currency, mv_line_dict['debit'] / st_line_currency_rate)
credit_at_current_rate = self.pool.get('res.currency').round(cr, uid, company_currency, mv_line_dict['credit'] / st_line_currency_rate) credit_at_current_rate = self.pool.get('res.currency').round(cr, uid, company_currency, mv_line_dict['credit'] / st_line_currency_rate)
elif st_line.currency_id and st_line_currency_rate: elif st_line.currency_id and st_line_currency_rate:
debit_at_current_rate = currency_obj.compute(cr, uid, statement_currency.id, company_currency.id, mv_line_dict['debit'] / st_line_currency_rate, context=context) debit_at_current_rate = currency_obj.compute(cr, uid, statement_currency.id, company_currency.id, mv_line_dict['debit'] / st_line_currency_rate, context=ctx)
credit_at_current_rate = currency_obj.compute(cr, uid, statement_currency.id, company_currency.id, mv_line_dict['credit'] / st_line_currency_rate, context=context) credit_at_current_rate = currency_obj.compute(cr, uid, statement_currency.id, company_currency.id, mv_line_dict['credit'] / st_line_currency_rate, context=ctx)
else: else:
debit_at_current_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['debit'], context=context) debit_at_current_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['debit'], context=ctx)
credit_at_current_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['credit'], context=context) credit_at_current_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['credit'], context=ctx)
if mv_line_dict.get('counterpart_move_line_id'): if mv_line_dict.get('counterpart_move_line_id'):
#post an account line that use the same currency rate than the counterpart (to balance the account) and post the difference in another line #post an account line that use the same currency rate than the counterpart (to balance the account) and post the difference in another line
ctx = context.copy()
ctx['date'] = mv_line.date ctx['date'] = mv_line.date
debit_at_old_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['debit'], context=ctx) debit_at_old_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['debit'], context=ctx)
credit_at_old_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['credit'], context=ctx) credit_at_old_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['credit'], context=ctx)
@ -790,7 +723,7 @@ class account_bank_statement_line(osv.osv):
mv_line_dict['debit'] = debit_at_old_rate mv_line_dict['debit'] = debit_at_old_rate
if debit_at_old_rate - debit_at_current_rate: if debit_at_old_rate - debit_at_current_rate:
currency_diff = debit_at_current_rate - debit_at_old_rate currency_diff = debit_at_current_rate - debit_at_old_rate
to_create.append(self.get_currency_rate_line(cr, uid, st_line, currency_diff, move_id, context=context)) to_create.append(self.get_currency_rate_line(cr, uid, st_line, -currency_diff, move_id, context=context))
if credit_at_old_rate - credit_at_current_rate: if credit_at_old_rate - credit_at_current_rate:
currency_diff = credit_at_current_rate - credit_at_old_rate currency_diff = credit_at_current_rate - credit_at_old_rate
to_create.append(self.get_currency_rate_line(cr, uid, st_line, currency_diff, move_id, context=context)) to_create.append(self.get_currency_rate_line(cr, uid, st_line, currency_diff, move_id, context=context))
@ -812,12 +745,9 @@ class account_bank_statement_line(osv.osv):
new_aml_id = aml_obj.create(cr, uid, mv_line_dict, context=context) new_aml_id = aml_obj.create(cr, uid, mv_line_dict, context=context)
if counterpart_move_line_id != None: if counterpart_move_line_id != None:
move_line_pairs_to_reconcile.append([new_aml_id, counterpart_move_line_id]) move_line_pairs_to_reconcile.append([new_aml_id, counterpart_move_line_id])
# Reconcile # Reconcile
for pair in move_line_pairs_to_reconcile: for pair in move_line_pairs_to_reconcile:
# TODO : too slow
aml_obj.reconcile_partial(cr, uid, pair, context=context) aml_obj.reconcile_partial(cr, uid, pair, context=context)
# Mark the statement line as reconciled # Mark the statement line as reconciled
self.write(cr, uid, id, {'journal_entry_id': move_id}, context=context) self.write(cr, uid, id, {'journal_entry_id': move_id}, context=context)
@ -826,7 +756,7 @@ class account_bank_statement_line(osv.osv):
# Unfortunately, that spawns a "no access rights" error ; it shouldn't. # Unfortunately, that spawns a "no access rights" error ; it shouldn't.
def _needaction_domain_get(self, cr, uid, context=None): def _needaction_domain_get(self, cr, uid, context=None):
user = self.pool.get("res.users").browse(cr, uid, uid) user = self.pool.get("res.users").browse(cr, uid, uid)
return ['|',('company_id','=',False),('company_id','child_of',[user.company_id.id]),('journal_entry_id', '=', False)] return ['|', ('company_id', '=', False), ('company_id', 'child_of', [user.company_id.id]), ('journal_entry_id', '=', False)]
_order = "statement_id desc, sequence" _order = "statement_id desc, sequence"
_name = "account.bank.statement.line" _name = "account.bank.statement.line"
@ -864,13 +794,13 @@ class account_statement_operation_template(osv.osv):
'label': fields.char('Label'), 'label': fields.char('Label'),
'amount_type': fields.selection([('fixed', 'Fixed'),('percentage_of_total','Percentage of total amount'),('percentage_of_balance', 'Percentage of open balance')], 'amount_type': fields.selection([('fixed', 'Fixed'),('percentage_of_total','Percentage of total amount'),('percentage_of_balance', 'Percentage of open balance')],
'Amount type', required=True), 'Amount type', required=True),
'amount': fields.float('Amount', digits_compute=dp.get_precision('Account'), help="Leave to 0 to ignore."), 'amount': fields.float('Amount', digits_compute=dp.get_precision('Account'), help="The amount will count as a debit if it is negative, as a credit if it is positive (except if amount type is 'Percentage of open balance').", required=True),
'tax_id': fields.many2one('account.tax', 'Tax', ondelete='cascade'), 'tax_id': fields.many2one('account.tax', 'Tax', ondelete='cascade'),
'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete='cascade'), 'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete='cascade'),
} }
_defaults = { _defaults = {
'amount_type': 'fixed', 'amount_type': 'percentage_of_balance',
'amount': 0.0 'amount': 100.0
} }
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -645,10 +645,6 @@ class account_invoice(models.Model):
invoice.check_total = invoice.amount_total invoice.check_total = invoice.amount_total
return True return True
@staticmethod
def _convert_ref(ref):
return (ref or '').replace('/','')
@api.multi @api.multi
def _get_analytic_lines(self): def _get_analytic_lines(self):
""" Return a list of dict for creating analytic lines for self[0] """ """ Return a list of dict for creating analytic lines for self[0] """
@ -661,7 +657,7 @@ class account_invoice(models.Model):
if self.type in ('in_invoice', 'in_refund'): if self.type in ('in_invoice', 'in_refund'):
ref = self.reference ref = self.reference
else: else:
ref = self._convert_ref(self.number) ref = self.number
if not self.journal_id.analytic_journal_id: if not self.journal_id.analytic_journal_id:
raise except_orm(_('No Analytic Journal!'), raise except_orm(_('No Analytic Journal!'),
_("You have to define an analytic journal on the '%s' journal!") % (self.journal_id.name,)) _("You have to define an analytic journal on the '%s' journal!") % (self.journal_id.name,))
@ -827,7 +823,7 @@ class account_invoice(models.Model):
if inv.type in ('in_invoice', 'in_refund'): if inv.type in ('in_invoice', 'in_refund'):
ref = inv.reference ref = inv.reference
else: else:
ref = self._convert_ref(inv.number) ref = inv.number
diff_currency = inv.currency_id != company_currency diff_currency = inv.currency_id != company_currency
# create one move line for the total and possibly adjust the other lines amount # create one move line for the total and possibly adjust the other lines amount
@ -955,11 +951,11 @@ class account_invoice(models.Model):
if inv.type in ('in_invoice', 'in_refund'): if inv.type in ('in_invoice', 'in_refund'):
if not inv.reference: if not inv.reference:
ref = self._convert_ref(inv.number) ref = inv.number
else: else:
ref = inv.reference ref = inv.reference
else: else:
ref = self._convert_ref(inv.number) ref = inv.number
self._cr.execute(""" UPDATE account_move SET ref=%s self._cr.execute(""" UPDATE account_move SET ref=%s
WHERE id=%s AND (ref IS NULL OR ref = '')""", WHERE id=%s AND (ref IS NULL OR ref = '')""",
@ -1131,7 +1127,7 @@ class account_invoice(models.Model):
if self.type in ('in_invoice', 'in_refund'): if self.type in ('in_invoice', 'in_refund'):
ref = self.reference ref = self.reference
else: else:
ref = self._convert_ref(self.number) ref = self.number
partner = self.partner_id._find_accounting_partner(self.partner_id) partner = self.partner_id._find_accounting_partner(self.partner_id)
name = name or self.invoice_line.name or self.number name = name or self.invoice_line.name or self.number
# Pay attention to the sign for both debit/credit AND amount_currency # Pay attention to the sign for both debit/credit AND amount_currency

View File

@ -22,12 +22,12 @@
import time import time
from datetime import datetime from datetime import datetime
from openerp import workflow from openerp import workflow
from openerp.osv import fields, osv from openerp.osv import fields, osv
from openerp.tools.translate import _ from openerp.tools.translate import _
import openerp.addons.decimal_precision as dp import openerp.addons.decimal_precision as dp
from openerp import tools from openerp import tools
from openerp.report import report_sxw
import openerp import openerp
class account_move_line(osv.osv): class account_move_line(osv.osv):
@ -752,6 +752,74 @@ class account_move_line(osv.osv):
args.append(('partner_id', '=', partner[0])) args.append(('partner_id', '=', partner[0]))
return super(account_move_line, self).search(cr, uid, args, offset, limit, order, context, count) return super(account_move_line, self).search(cr, uid, args, offset, limit, order, context, count)
def prepare_move_lines_for_reconciliation_widget(self, cr, uid, lines, target_currency=False, target_date=False, context=None):
""" Returns move lines formatted for the manual/bank reconciliation widget
:param target_currency: curreny you want the move line debit/credit converted into
:param target_date: date to use for the monetary conversion
"""
if not lines:
return []
if context is None:
context = {}
ctx = context.copy()
currency_obj = self.pool.get('res.currency')
company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id
rml_parser = report_sxw.rml_parse(cr, uid, 'reconciliation_widget_aml', context=context)
reconcile_partial_ids = [] # for a partial reconciliation, take only one line
ret = []
for line in lines:
if line.reconcile_partial_id and line.reconcile_partial_id.id in reconcile_partial_ids:
continue
if line.reconcile_partial_id:
reconcile_partial_ids.append(line.reconcile_partial_id.id)
ret_line = {
'id': line.id,
'name': line.move_id.name,
'ref': line.move_id.ref,
'account_code': line.account_id.code,
'account_name': line.account_id.name,
'account_type': line.account_id.type,
'date_maturity': line.date_maturity,
'date': line.date,
'period_name': line.period_id.name,
'journal_name': line.journal_id.name,
'partner_id': line.partner_id.id,
'partner_name': line.partner_id.name,
}
# Get right debit / credit:
line_currency = line.currency_id or company_currency
amount_currency_str = ""
if line.currency_id and line.amount_currency:
amount_currency_str = rml_parser.formatLang(line.amount_currency, currency_obj=line.currency_id)
if target_currency and line_currency == target_currency and target_currency != company_currency:
debit = line.debit > 0 and line.amount_residual_currency or 0.0
credit = line.credit > 0 and line.amount_residual_currency or 0.0
amount_currency_str = rml_parser.formatLang(line.amount_residual, currency_obj=company_currency)
amount_str = rml_parser.formatLang(debit or credit, currency_obj=target_currency)
else:
debit = line.debit > 0 and line.amount_residual or 0.0
credit = line.credit > 0 and line.amount_residual or 0.0
amount_str = rml_parser.formatLang(debit or credit, currency_obj=company_currency)
if target_currency and target_currency != company_currency:
amount_currency_str = rml_parser.formatLang(debit or credit, currency_obj=line_currency)
ctx = context.copy()
if target_date:
ctx.update({'date': target_date})
debit = currency_obj.compute(cr, uid, target_currency.id, company_currency.id, debit, context=ctx)
credit = currency_obj.compute(cr, uid, target_currency.id, company_currency.id, credit, context=ctx)
amount_str = rml_parser.formatLang(debit or credit, currency_obj=target_currency)
ret_line['credit'] = credit
ret_line['debit'] = debit
ret_line['amount_str'] = amount_str
ret_line['amount_currency_str'] = amount_currency_str
ret.append(ret_line)
return ret
def list_partners_to_reconcile(self, cr, uid, context=None): def list_partners_to_reconcile(self, cr, uid, context=None):
cr.execute( cr.execute(
"""SELECT partner_id FROM ( """SELECT partner_id FROM (

View File

@ -493,7 +493,7 @@
<record id="action_bank_reconcile_bank_statements" model="ir.actions.client"> <record id="action_bank_reconcile_bank_statements" model="ir.actions.client">
<field name="name">Reconciliation on Bank Statements</field> <field name="name">Reconciliation on Bank Statements</field>
<field name="tag">bank_statement_reconciliation_view</field> <field name="tag">bank_statement_reconciliation_view</field>
<field name="context">{'statement_id': active_id}</field> <field name="context">{'statement_ids': [active_id]}</field>
</record> </record>
<record id="view_account_bank_statement_filter" model="ir.ui.view"> <record id="view_account_bank_statement_filter" model="ir.ui.view">

View File

@ -30,7 +30,7 @@
<field name="statement_id" ref="demo_bank_statement_1"/> <field name="statement_id" ref="demo_bank_statement_1"/>
<field name="sequence" eval="2"/> <field name="sequence" eval="2"/>
<field name="company_id" ref="base.main_company"/> <field name="company_id" ref="base.main_company"/>
<field name="name">SAJ2014002</field> <field name="name">SAJ/2014/002</field>
<field name="journal_id" ref="account.bank_journal"/> <field name="journal_id" ref="account.bank_journal"/>
<field name="amount" eval="650.0"/> <field name="amount" eval="650.0"/>
<field name="date" eval="time.strftime('%Y')+'-01-01'"/> <field name="date" eval="time.strftime('%Y')+'-01-01'"/>
@ -70,15 +70,15 @@
<field name="name">Bank Fees</field> <field name="name">Bank Fees</field>
<field name="account_id" ref="a_expense"></field> <field name="account_id" ref="a_expense"></field>
<field name="label">Bank Fees</field> <field name="label">Bank Fees</field>
<field name="amount_type">fixed</field> <field name="amount_type">percentage_of_balance</field>
<field name="amount"></field> <field name="amount">100.0</field>
</record> </record>
<record id="statement_operation_template_3" model="account.statement.operation.template"> <record id="statement_operation_template_3" model="account.statement.operation.template">
<field name="name">Profit / Loss</field> <field name="name">Profit / Loss</field>
<field name="account_id" ref="a_sale"></field> <field name="account_id" ref="a_sale"></field>
<field name="label">Profit / Loss</field> <field name="label">Profit / Loss</field>
<field name="amount_type">fixed</field> <field name="amount_type">percentage_of_balance</field>
<field name="amount"></field> <field name="amount">100.0</field>
</record> </record>
</data> </data>
</openerp> </openerp>

View File

@ -125,7 +125,29 @@ class account_config_settings(osv.osv_memory):
help="Allows you to use the analytic accounting."), help="Allows you to use the analytic accounting."),
'group_check_supplier_invoice_total': fields.boolean('Check the total of supplier invoices', 'group_check_supplier_invoice_total': fields.boolean('Check the total of supplier invoices',
implied_group="account.group_supplier_inv_check_total"), implied_group="account.group_supplier_inv_check_total"),
'income_currency_exchange_account_id': fields.related(
'company_id', 'income_currency_exchange_account_id',
type='many2one',
relation='account.account',
string="Gain Exchange Rate Account",
domain="[('type', '=', 'other')]"),
'expense_currency_exchange_account_id': fields.related(
'company_id', 'expense_currency_exchange_account_id',
type="many2one",
relation='account.account',
string="Loss Exchange Rate Account",
domain="[('type', '=', 'other')]"),
} }
def onchange_company_id(self, cr, uid, ids, company_id, context=None):
res = super(account_config_settings, self).onchange_company_id(cr, uid, ids, company_id, context=context)
if company_id:
company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
res['value'].update({'income_currency_exchange_account_id': company.income_currency_exchange_account_id and company.income_currency_exchange_account_id.id or False,
'expense_currency_exchange_account_id': company.expense_currency_exchange_account_id and company.expense_currency_exchange_account_id.id or False})
else:
res['value'].update({'income_currency_exchange_account_id': False,
'expense_currency_exchange_account_id': False})
return res
def _default_company(self, cr, uid, context=None): def _default_company(self, cr, uid, context=None):
user = self.pool.get('res.users').browse(cr, uid, uid, context=context) user = self.pool.get('res.users').browse(cr, uid, uid, context=context)

View File

@ -124,6 +124,14 @@
<div name="group_multi_currency"> <div name="group_multi_currency">
<field name="group_multi_currency" class="oe_inline" on_change="onchange_multi_currency(group_multi_currency)"/> <field name="group_multi_currency" class="oe_inline" on_change="onchange_multi_currency(group_multi_currency)"/>
<label for="group_multi_currency"/> <label for="group_multi_currency"/>
<group attrs="{'invisible': [('group_multi_currency', '&lt;&gt;', True)]}" col="2">
<group>
<field name="income_currency_exchange_account_id"/>
<field name="expense_currency_exchange_account_id"/>
</group>
<group>
</group>
</group>
</div> </div>
<div> <div>
<field name="module_account_accountant" class="oe_inline"/> <field name="module_account_accountant" class="oe_inline"/>

View File

@ -6,18 +6,29 @@
-o-user-select: none; -o-user-select: none;
user-select: none; user-select: none;
cursor: default; cursor: default;
height: 100%;
/* icons */ } /* icons */ }
.openerp .oe_bank_statement_reconciliation .oe_form_sheetbg {
border-bottom: 0;
padding: 0;
height: 100%; }
.openerp .oe_bank_statement_reconciliation .oe_form_sheetbg .oe_form_sheet {
position: relative;
padding: 20px 15px 30px 15px;
border-top: 0;
border-bottom: 0;
height: 100%; }
.openerp .oe_bank_statement_reconciliation h1 { .openerp .oe_bank_statement_reconciliation h1 {
width: 48%; width: 48%;
padding: 0 0 0 15px; padding: 0 0 0 15px;
margin: 0 0 35px 0; margin: 0 0 25px 0;
float: left; float: left;
font-size: 2.3em; } font-size: 2em; }
.openerp .oe_bank_statement_reconciliation h2 { .openerp .oe_bank_statement_reconciliation h2 {
font-size: 1.8em; } font-size: 1.8em; }
.openerp .oe_bank_statement_reconciliation .progress { .openerp .oe_bank_statement_reconciliation .progress {
width: 49%; width: 49%;
margin: 6px 15px 0 0; margin: 4px 15px 0 0;
float: right; float: right;
position: relative; position: relative;
display: inline-block; } display: inline-block; }
@ -29,9 +40,6 @@
top: 2px; top: 2px;
z-index: 10; z-index: 10;
text-shadow: -1px -1px 0 #f5f5f5, 1px -1px 0 #f5f5f5, -1px 1px 0 #f5f5f5, 1px 1px 0 #f5f5f5; } text-shadow: -1px -1px 0 #f5f5f5, 1px -1px 0 #f5f5f5, -1px 1px 0 #f5f5f5, 1px 1px 0 #f5f5f5; }
.openerp .oe_bank_statement_reconciliation .oe_form_sheet {
position: relative;
padding-bottom: 30px; }
.openerp .oe_bank_statement_reconciliation .protip { .openerp .oe_bank_statement_reconciliation .protip {
margin: 0; margin: 0;
position: absolute; position: absolute;
@ -100,7 +108,12 @@
-moz-transform: rotate(0deg); -moz-transform: rotate(0deg);
-ms-transform: rotate(0deg); -ms-transform: rotate(0deg);
-o-transform: rotate(0deg); -o-transform: rotate(0deg);
transform: rotate(0deg); } transform: rotate(0deg);
-webkit-transition-duration: 300ms;
-moz-transition-duration: 300ms;
-ms-transition-duration: 300ms;
-o-transition-duration: 300ms;
transition-duration: 300ms; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .visible_toggle, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line[data-mode="match"] .toggle_match, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line[data-mode="create"] .toggle_create { .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .visible_toggle, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line[data-mode="match"] .toggle_match, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line[data-mode="create"] .toggle_create {
visibility: visible !important; visibility: visible !important;
-webkit-transform: rotate(90deg); -webkit-transform: rotate(90deg);
@ -136,24 +149,24 @@
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match table td { .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match table td {
padding: 1px 8px; padding: 1px 8px;
vertical-align: middle; } vertical-align: middle; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td:nth-child(1), .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td:nth-child(7), .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match table td:nth-child(1), .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match table td:nth-child(7) { .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td.cell_action, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td.cell_info_popover, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match table td.cell_action, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match table td.cell_info_popover {
width: 15px; width: 15px;
padding: 0; } padding: 0; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td:nth-child(1), .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match table td:nth-child(1) { .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td.cell_action, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match table td.cell_action {
text-align: left; } text-align: left; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td:nth-child(2), .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match table td:nth-child(2) { .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td.cell_account_code, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match table td.cell_account_code {
width: 80px; width: 80px;
padding-left: 3px; } padding-left: 3px; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td:nth-child(3), .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match table td:nth-child(3) { .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td.cell_due_date, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match table td.cell_due_date {
width: 100px; } width: 100px; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td:nth-child(5), .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match table td:nth-child(5) { .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td.cell_debit, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match table td.cell_debit {
text-align: right; text-align: right;
width: 15%; } width: 15%; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td:nth-child(6), .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match table td:nth-child(6) { .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td.cell_credit, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match table td.cell_credit {
width: 15%; width: 15%;
text-align: right; text-align: right;
padding-right: 3px; } padding-right: 3px; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td:nth-child(7), .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match table td:nth-child(7) { .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td.cell_info_popover, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match table td.cell_info_popover {
text-align: right; } text-align: right; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view tr.line_open_balance, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match table tr.line_open_balance { .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view tr.line_open_balance, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match table tr.line_open_balance {
color: #bbb; } color: #bbb; }
@ -176,8 +189,13 @@
-webkit-transition-property: background-color; -webkit-transition-property: background-color;
-moz-transition-property: background-color; -moz-transition-property: background-color;
-ms-transition-property: background-color; -ms-transition-property: background-color;
transition-property: background-color; } transition-property: background-color;
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view .initial_line > td:nth-child(1), .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view .initial_line > td:nth-child(7) { -webkit-transition-duration: 300ms;
-moz-transition-duration: 300ms;
-ms-transition-duration: 300ms;
-o-transition-duration: 300ms;
transition-duration: 300ms; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view .initial_line > td.cell_action, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view .initial_line > td.cell_info_popover {
border-top: none; border-top: none;
background: white !important; background: white !important;
padding-top: 6px; padding-top: 6px;
@ -188,9 +206,9 @@
font-weight: bold; font-weight: bold;
height: 26px; height: 26px;
margin: 0 15px 4px 15px; } margin: 0 15px 4px 15px; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view caption .button_ok { .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view caption button {
float: right; } float: right; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view caption .button_ok:disabled { .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view caption button:disabled {
opacity: 0.5; } opacity: 0.5; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view caption > span, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view caption > input { .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view caption > span, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view caption > input {
position: relative; position: relative;
@ -198,7 +216,7 @@
/* meh */ /* meh */
font-weight: bold; font-weight: bold;
cursor: pointer; } cursor: pointer; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td:nth-child(6) { .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td.cell_credit {
border-left: 1px solid black; } border-left: 1px solid black; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match .match_controls { .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match .match_controls {
padding: 0 0 5px 18px; } padding: 0 0 5px 18px; }

View File

@ -3,6 +3,8 @@ $mainTableBordersPadding: 3px;
$lightBorder: 1px solid #bbb; $lightBorder: 1px solid #bbb;
$accountingBorder: 1px solid #000; $accountingBorder: 1px solid #000;
$initialLineBackground: #f0f0f0; $initialLineBackground: #f0f0f0;
// Warning, this value is also specified in the instance.web.account.abstractReconciliation widget
$aestetic_animation_speed: 300ms;
.openerp .oe_bank_statement_reconciliation { .openerp .oe_bank_statement_reconciliation {
@ -13,13 +15,28 @@ $initialLineBackground: #f0f0f0;
-o-user-select: none; -o-user-select: none;
user-select: none; user-select: none;
cursor: default; cursor: default;
height: 100%;
.oe_form_sheetbg {
border-bottom: 0;
padding: 0;
height: 100%;
.oe_form_sheet {
position: relative;
padding: 20px 15px 30px 15px;
border-top: 0;
border-bottom: 0;
height: 100%;
}
}
h1 { h1 {
width: 48%; width: 48%;
padding: 0 0 0 $actionColWidth; padding: 0 0 0 $actionColWidth;
margin: 0 0 35px 0; margin: 0 0 25px 0;
float: left; float: left;
font-size: 2.3em; font-size: 2em;
} }
h2 { h2 {
@ -28,7 +45,7 @@ $initialLineBackground: #f0f0f0;
.progress { .progress {
width: 49%; width: 49%;
margin: 6px $actionColWidth 0 0; margin: 4px $actionColWidth 0 0;
float: right; float: right;
position: relative; position: relative;
display: inline-block; display: inline-block;
@ -48,11 +65,6 @@ $initialLineBackground: #f0f0f0;
} }
} }
.oe_form_sheet {
position: relative;
padding-bottom: 30px;
}
.protip { .protip {
margin: 0; margin: 0;
position: absolute; position: absolute;
@ -145,6 +157,11 @@ $initialLineBackground: #f0f0f0;
-ms-transform: rotate(0deg); -ms-transform: rotate(0deg);
-o-transform: rotate(0deg); -o-transform: rotate(0deg);
transform: rotate(0deg); transform: rotate(0deg);
-webkit-transition-duration: $aestetic_animation_speed;
-moz-transition-duration: $aestetic_animation_speed;
-ms-transition-duration: $aestetic_animation_speed;
-o-transition-duration: $aestetic_animation_speed;
transition-duration: $aestetic_animation_speed;
} }
.visible_toggle { .visible_toggle {
@ -228,40 +245,40 @@ $initialLineBackground: #f0f0f0;
vertical-align: middle; vertical-align: middle;
} }
td:nth-child(1), td:nth-child(7) { td.cell_action, td.cell_info_popover {
width: $actionColWidth; width: $actionColWidth;
padding: 0; padding: 0;
} }
td:nth-child(1) { td.cell_action {
text-align: left; text-align: left;
} }
td:nth-child(2) { td.cell_account_code {
width: 80px; width: 80px;
padding-left: $mainTableBordersPadding; padding-left: $mainTableBordersPadding;
} }
td:nth-child(3) { td.cell_due_date {
width: 100px; width: 100px;
} }
td:nth-child(4) { td.cell_label {
} }
td:nth-child(5) { td.cell_debit {
text-align: right; text-align: right;
width: 15%; width: 15%;
} }
td:nth-child(6) { td.cell_credit {
width: 15%; width: 15%;
text-align: right; text-align: right;
padding-right: $mainTableBordersPadding; padding-right: $mainTableBordersPadding;
} }
td:nth-child(7) { td.cell_info_popover {
text-align: right; text-align: right;
} }
@ -301,8 +318,13 @@ $initialLineBackground: #f0f0f0;
-moz-transition-property: background-color; -moz-transition-property: background-color;
-ms-transition-property: background-color; -ms-transition-property: background-color;
transition-property: background-color; transition-property: background-color;
-webkit-transition-duration: $aestetic_animation_speed;
-moz-transition-duration: $aestetic_animation_speed;
-ms-transition-duration: $aestetic_animation_speed;
-o-transition-duration: $aestetic_animation_speed;
transition-duration: $aestetic_animation_speed;
&:nth-child(1), &:nth-child(7) { &.cell_action, &.cell_info_popover {
border-top: none; border-top: none;
background: white !important; background: white !important;
// Hack pour l'alignement au px près // Hack pour l'alignement au px près
@ -318,7 +340,7 @@ $initialLineBackground: #f0f0f0;
height: 26px; height: 26px;
margin: 0 $actionColWidth 4px $actionColWidth; margin: 0 $actionColWidth 4px $actionColWidth;
.button_ok { button {
float: right; float: right;
&:disabled { &:disabled {
@ -334,7 +356,7 @@ $initialLineBackground: #f0f0f0;
} }
// accounting "T" // accounting "T"
td:nth-child(6) { border-left: $accountingBorder; } td.cell_credit { border-left: $accountingBorder; }
} }

View File

@ -0,0 +1,147 @@
(function () {
'use strict';
var _t = openerp._t;
openerp.Tour.register({
id: 'bank_statement_reconciliation',
name: _t("Reconcile the demo bank statement"),
path: '/web',
mode: 'test',
// TODO : identify menu by data-menu attr or text node ?
steps: [
// Go to the first statement reconciliation
{
title: "go to accounting",
element: '.oe_menu_toggler:contains("Accounting"):visible',
},
{
title: "go to bank statements",
element: '.oe_menu_leaf:contains("Bank Statement"):visible',
},
{
title: "select first bank statement",
element: '.oe_list_content tbody tr:contains("BNK/2014/001")',
},
{
title: "click the reconcile button",
element: '.oe_form_container header button:contains("Reconcile")',
},
// Check mutual exclusion of move lines
{
title: "set second reconciliation in match mode",
element: '.oe_bank_statement_reconciliation_line:nth-child(2) .initial_line'
},
{
title: "deselect SAJ/2014/002 from second reconciliation",
element: '.oe_bank_statement_reconciliation_line:nth-child(2) .accounting_view .mv_line:contains("SAJ/2014/002")'
},
{
title: "check it appeared in first reconciliation's matches list and select SAJ/2014/002 in second reconciliation",
waitNot: '.oe_bank_statement_reconciliation_line:nth-child(2) .accounting_view .mv_line:contains("SAJ/2014/002")',
waitFor: '.oe_bank_statement_reconciliation_line:first-child .mv_line:contains("SAJ/2014/002")',
element: '.oe_bank_statement_reconciliation_line:nth-child(2) .mv_line:contains("SAJ/2014/002")'
},
// Make a partial reconciliation
{
title: "select SAJ/2014/001",
element: '.oe_bank_statement_reconciliation_line:first-child .mv_line:contains("SAJ/2014/001")'
},
{
title: "click on the partial reconciliation button",
element: '.oe_bank_statement_reconciliation_line:first-child .mv_line:contains("SAJ/2014/001") .do_partial_reconcile_button'
},
{
title: "click on the OK button",
element: '.oe_bank_statement_reconciliation_line:first-child .button_ok.oe_highlight'
},
// Test changing the partner
{
title: "change the partner (1)",
waitNot: '.oe_bank_statement_reconciliation_line:nth-child(4)', // wait for the reconciliation to be processed
element: '.oe_bank_statement_reconciliation_line:first-child .partner_name'
},
{
title: "change the partner (2)",
element: '.oe_bank_statement_reconciliation_line:first-child .change_partner_container input',
sampleText: 'Vauxoo',
},
{
title: "change the partner (3)",
element: '.ui-autocomplete .ui-menu-item:contains("Vauxoo")'
},
{
title: "check the reconciliation is reloaded and has no match",
element: '.oe_bank_statement_reconciliation_line:first-child.no_match',
},
{
title: "change the partner back (1)",
element: '.oe_bank_statement_reconciliation_line:first-child .partner_name'
},
{
title: "change the partner back (2)",
element: '.oe_bank_statement_reconciliation_line:first-child .change_partner_container input',
sampleText: 'Best Designers',
},
{
title: "change the partner back (3)",
element: '.ui-autocomplete .ui-menu-item:contains("Best Designers")'
},
{
title: "select SAJ/2014/002",
element: '.oe_bank_statement_reconciliation_line:first-child .mv_line:contains("SAJ/2014/002")'
},
{
title: "click on the OK button",
element: '.oe_bank_statement_reconciliation_line:first-child .button_ok.oe_highlight'
},
// Create a new move line in first reconciliation and validate it
{
title: "check following reconciliation passes in mode create",
waitNot: '.oe_bank_statement_reconciliation_line:nth-child(3)', // wait for the reconciliation to be processed
element: '.oe_bank_statement_reconciliation_line:first-child[data-mode="create"]'
},
{
title: "click the Profit/Loss preset",
element: '.oe_bank_statement_reconciliation_line:first-child button:contains("Profit / Loss")'
},
{
title: "click on the OK button",
element: '.oe_bank_statement_reconciliation_line:first-child .button_ok.oe_highlight'
},
// Leave an open balance
{
title: "select SAJ/2014/003",
waitNot: '.oe_bank_statement_reconciliation_line:nth-child(2)', // wait for the reconciliation to be processed
element: '.oe_bank_statement_reconciliation_line:first-child .mv_line:contains("SAJ/2014/003")'
},
{
title: "click on the Keep Open button",
element: '.oe_bank_statement_reconciliation_line:first-child .button_ok:not(.oe_highlight)'
},
// Be done
{
title: "check 'finish screen' and close the statement",
waitFor: '.done_message',
element: '.button_close_statement'
},
{
title: "check the statement is closed",
element: '.oe_form_container header .label:contains("Closed")'
},
]
});
}());

View File

@ -13,7 +13,8 @@ openerp.account = function (instance) {
init: function(parent, context) { init: function(parent, context) {
this._super(parent); this._super(parent);
this.max_reconciliations_displayed = 10; this.max_reconciliations_displayed = 10;
this.statement_id = context.context.statement_id; if (context.context.statement_id) this.statement_ids = [context.context.statement_id];
if (context.context.statement_ids) this.statement_ids = context.context.statement_ids;
this.title = context.context.title || _t("Reconciliation"); this.title = context.context.title || _t("Reconciliation");
this.st_lines = []; this.st_lines = [];
this.last_displayed_reconciliation_index = undefined; // Flow control this.last_displayed_reconciliation_index = undefined; // Flow control
@ -37,7 +38,7 @@ openerp.account = function (instance) {
// We'll need to get the code of an account selected in a many2one (whose value is the id) // We'll need to get the code of an account selected in a many2one (whose value is the id)
this.map_account_id_code = {}; this.map_account_id_code = {};
// The same move line cannot be selected for multiple resolutions // The same move line cannot be selected for multiple resolutions
this.excluded_move_lines_ids = []; this.excluded_move_lines_ids = {};
// Description of the fields to initialize in the "create new line" form // Description of the fields to initialize in the "create new line" form
// NB : for presets to work correctly, a field id must be the same string as a preset field // NB : for presets to work correctly, a field id must be the same string as a preset field
this.create_form_fields = { this.create_form_fields = {
@ -118,48 +119,35 @@ openerp.account = function (instance) {
start: function() { start: function() {
this._super(); this._super();
var self = this; var self = this;
// Inject variable styles
var style = document.createElement("style");
style.appendChild(document.createTextNode(""));
document.head.appendChild(style);
var css_selector = ".oe_bank_statement_reconciliation_line .toggle_match, .oe_bank_statement_reconciliation_line .toggle_create, .oe_bank_statement_reconciliation_line .initial_line > td";
if(style.sheet.insertRule) {
style.sheet.insertRule(css_selector + " { -webkit-transition-duration: "+self.aestetic_animation_speed+"ms; }", 0);
style.sheet.insertRule(css_selector + " { -moz-transition-duration: "+self.aestetic_animation_speed+"ms; }", 0);
style.sheet.insertRule(css_selector + " { -ms-transition-duration: "+self.aestetic_animation_speed+"ms; }", 0);
style.sheet.insertRule(css_selector + " { -o-transition-duration: "+self.aestetic_animation_speed+"ms; }", 0);
style.sheet.insertRule(css_selector + " { transition-duration: "+self.aestetic_animation_speed+"ms; }", 0);
} else {
style.sheet.addRule(css_selector, "-webkit-transition-duration: "+self.aestetic_animation_speed+"ms;");
style.sheet.addRule(css_selector, "-moz-transition-duration: "+self.aestetic_animation_speed+"ms;");
style.sheet.addRule(css_selector, "-ms-transition-duration: "+self.aestetic_animation_speed+"ms;");
style.sheet.addRule(css_selector, "-o-transition-duration: "+self.aestetic_animation_speed+"ms;");
style.sheet.addRule(css_selector, "-webkit-transition-duration: "+self.aestetic_animation_speed+"ms;");
}
// Retreive statement infos and reconciliation data from the model // Retreive statement infos and reconciliation data from the model
var lines_filter = [['journal_entry_id', '=', false], ['account_id', '=', false]]; var lines_filter = [['journal_entry_id', '=', false], ['account_id', '=', false]];
var deferred_promises = []; var deferred_promises = [];
if (self.statement_id) { // Working on specified statement(s)
lines_filter.push(['statement_id', '=', self.statement_id]); if (self.statement_ids && self.statement_ids.length > 0) {
lines_filter.push(['statement_id', 'in', self.statement_ids]);
// If only one statement, retreive its name
if (self.statement_ids.length === 1) {
deferred_promises.push(self.model_bank_statement deferred_promises.push(self.model_bank_statement
.query(["name"]) .query(["name"])
.filter([['id', '=', self.statement_id]]) .filter([['id', '=', self.statement_ids[0]]])
.first() .first()
.then(function(title){ .then(function(title){
self.title = title.name; self.title = title.name;
}) })
); );
}
// Anyway, find out how many statement lines are reconciled (for the progressbar)
deferred_promises.push(self.model_bank_statement deferred_promises.push(self.model_bank_statement
.call("number_of_lines_reconciled", [self.statement_id]) .call("number_of_lines_reconciled", [self.statement_ids])
.then(function(num) { .then(function(num) {
self.already_reconciled_lines = num; self.already_reconciled_lines = num;
}) })
); );
} }
// Get operation templates
deferred_promises.push(new instance.web.Model("account.statement.operation.template") deferred_promises.push(new instance.web.Model("account.statement.operation.template")
.query(['id','name','account_id','label','amount_type','amount','tax_id','analytic_account_id']) .query(['id','name','account_id','label','amount_type','amount','tax_id','analytic_account_id'])
.all().then(function (data) { .all().then(function (data) {
@ -169,17 +157,19 @@ openerp.account = function (instance) {
}) })
); );
deferred_promises.push(self.model_bank_statement // Get the function to format currencies
.call("get_format_currency_js_function", [self.statement_id]) deferred_promises.push(new instance.web.Model("res.currency")
.then(function(data){ .call("get_format_currencies_js_function")
.then(function(data) {
self.formatCurrency = new Function("amount, currency_id", data); self.formatCurrency = new Function("amount, currency_id", data);
}) })
); );
// Get statement lines
deferred_promises.push(self.model_bank_statement_line deferred_promises.push(self.model_bank_statement_line
.query(['id']) .query(['id'])
.filter(lines_filter) .filter(lines_filter)
.order_by('id') .order_by('statement_id, id')
.all().then(function (data) { .all().then(function (data) {
self.st_lines = _(data).map(function(o){ return o.id }); self.st_lines = _(data).map(function(o){ return o.id });
}) })
@ -255,33 +245,58 @@ openerp.account = function (instance) {
} }
}, },
excludeMoveLines: function(line_ids) { // Adds move line ids to the list of move lines not to fetch for a given partner
// This is required because the same move line cannot be selected for multiple reconciliation
excludeMoveLines: function(source_child, partner_id, line_ids) {
var self = this; var self = this;
var excluded_ids = this.excluded_move_lines_ids[partner_id];
var excluded_move_lines_changed = false;
_.each(line_ids, function(line_id){ _.each(line_ids, function(line_id){
line_id = parseInt(line_id); if (excluded_ids.indexOf(line_id) === -1) {
if (self.excluded_move_lines_ids.indexOf(line_id) === -1) { excluded_ids.push(line_id);
self.excluded_move_lines_ids.push(line_id); excluded_move_lines_changed = true;
} }
}); });
//update all children view if (! excluded_move_lines_changed)
return;
// Function that finds if an array of line objects contains at least a line identified by its id
var contains_lines = function(lines_array, line_ids) {
for (var i = 0; i < lines_array.length; i++)
for (var j = 0; j < line_ids.length; j++)
if (lines_array[i].id === line_ids[j])
return true;
return false;
};
// Update children if needed
_.each(self.getChildren(), function(child){ _.each(self.getChildren(), function(child){
child.render(); if (child.partner_id === partner_id && child !== source_child) {
if (contains_lines(child.get("mv_lines_selected"), line_ids)) {
child.set("mv_lines_selected", _.filter(child.get("mv_lines_selected"), function(o){ return line_ids.indexOf(o.id) === -1 }));
} else if (contains_lines(child.mv_lines_deselected, line_ids)) {
child.mv_lines_deselected = _.filter(child.mv_lines_deselected, function(o){ return line_ids.indexOf(o.id) === -1 });
child.updateMatches();
} else if (contains_lines(child.get("mv_lines"), line_ids)) {
child.updateMatches();
}
}
}); });
}, },
unexcludeMoveLines: function(line_ids) { unexcludeMoveLines: function(source_child, partner_id, line_ids) {
var self = this; var self = this;
var index = -1;
_.each(line_ids, function(line_id){ var initial_excluded_lines_num = this.excluded_move_lines_ids[partner_id].length;
line_id = parseInt(line_id); this.excluded_move_lines_ids[partner_id] = _.difference(this.excluded_move_lines_ids[partner_id], line_ids);
index = self.excluded_move_lines_ids.indexOf(line_id); if (this.excluded_move_lines_ids[partner_id].length === initial_excluded_lines_num)
if (index > -1) { return;
self.excluded_move_lines_ids.splice(index,1);
} // Update children if needed
});
//update all children view
_.each(self.getChildren(), function(child){ _.each(self.getChildren(), function(child){
child.render(); if (child.partner_id === partner_id && child !== source_child && (child.get("mode") === "match" || child.$el.hasClass("no_match")))
child.updateMatches();
}); });
}, },
@ -313,6 +328,11 @@ openerp.account = function (instance) {
if (self.last_displayed_reconciliation_index < self.st_lines.length) { if (self.last_displayed_reconciliation_index < self.st_lines.length) {
self.displayReconciliation(self.st_lines[self.last_displayed_reconciliation_index++], 'inactive'); self.displayReconciliation(self.st_lines[self.last_displayed_reconciliation_index++], 'inactive');
} }
// Congratulate the user if the work is done
if (self.reconciled_lines === self.st_lines.length) {
self.displayDoneMessage();
}
// Put the first line in match mode // Put the first line in match mode
if (self.reconciled_lines !== self.st_lines.length) { if (self.reconciled_lines !== self.st_lines.length) {
var first_child = self.getChildren()[0]; var first_child = self.getChildren()[0];
@ -320,15 +340,12 @@ openerp.account = function (instance) {
first_child.set("mode", "match"); first_child.set("mode", "match");
} }
} }
// Congratulate the user if the work is done
if (self.reconciled_lines === self.st_lines.length) {
self.displayDoneMessage();
}
}, },
displayDoneMessage: function() { displayDoneMessage: function() {
var self = this; var self = this;
var is_single_statement = self.statement_ids !== undefined && self.statement_ids.length === 1;
var sec_taken = Math.round((Date.now()-self.time_widget_loaded)/1000); var sec_taken = Math.round((Date.now()-self.time_widget_loaded)/1000);
var sec_per_item = Math.round(sec_taken/self.reconciled_lines); var sec_per_item = Math.round(sec_taken/self.reconciled_lines);
var achievements = []; var achievements = [];
@ -364,7 +381,7 @@ openerp.account = function (instance) {
transactions_done: self.reconciled_lines, transactions_done: self.reconciled_lines,
done_with_ctrl_enter: self.lines_reconciled_with_ctrl_enter, done_with_ctrl_enter: self.lines_reconciled_with_ctrl_enter,
achievements: achievements, achievements: achievements,
has_statement_id: self.statement_id !== undefined, has_statement_id: is_single_statement,
})); }));
// Animate it // Animate it
@ -383,11 +400,11 @@ openerp.account = function (instance) {
}); });
}); });
if (self.$(".button_close_statement").length !== 0) { if (is_single_statement && self.$(".button_close_statement").length !== 0) {
self.$(".button_close_statement").hide(); self.$(".button_close_statement").hide();
self.model_bank_statement self.model_bank_statement
.query(["balance_end_real", "balance_end"]) .query(["balance_end_real", "balance_end"])
.filter([['id', '=', self.statement_id]]) .filter([['id', '=', self.statement_ids[0]]])
.first() .first()
.then(function(data){ .then(function(data){
if (data.balance_end_real === data.balance_end) { if (data.balance_end_real === data.balance_end) {
@ -395,7 +412,7 @@ openerp.account = function (instance) {
self.$(".button_close_statement").click(function() { self.$(".button_close_statement").click(function() {
self.$(".button_close_statement").attr("disabled", "disabled"); self.$(".button_close_statement").attr("disabled", "disabled");
self.model_bank_statement self.model_bank_statement
.call("button_confirm_bank", [[self.statement_id]]) .call("button_confirm_bank", [[self.statement_ids[0]]])
.then(function () { .then(function () {
self.do_action({ self.do_action({
type: 'ir.actions.client', type: 'ir.actions.client',
@ -456,9 +473,12 @@ openerp.account = function (instance) {
init: function(parent, context) { init: function(parent, context) {
this._super(parent); this._super(parent);
this.formatCurrency = this.getParent().formatCurrency;
if (context.initial_data_provided) { if (context.initial_data_provided) {
// Process data // Process data
_(context.reconciliation_proposition).each(this.decorateMoveLine.bind(this)); _.each(context.reconciliation_proposition, function(line) {
this.decorateMoveLine(line, context.st_line.currency_id);
}, this);
this.set("mv_lines_selected", context.reconciliation_proposition); this.set("mv_lines_selected", context.reconciliation_proposition);
this.st_line = context.st_line; this.st_line = context.st_line;
this.partner_id = context.st_line.partner_id; this.partner_id = context.st_line.partner_id;
@ -466,7 +486,9 @@ openerp.account = function (instance) {
// Exclude selected move lines // Exclude selected move lines
var selected_line_ids = _(context.reconciliation_proposition).map(function(o){ return o.id }); var selected_line_ids = _(context.reconciliation_proposition).map(function(o){ return o.id });
this.getParent().excludeMoveLines(selected_line_ids); if (this.getParent().excluded_move_lines_ids[this.partner_id] === undefined)
this.getParent().excluded_move_lines_ids[this.partner_id] = [];
this.getParent().excludeMoveLines(this, this.partner_id, selected_line_ids);
} else { } else {
this.set("mv_lines_selected", []); this.set("mv_lines_selected", []);
this.st_line = undefined; this.st_line = undefined;
@ -483,29 +505,31 @@ openerp.account = function (instance) {
this.model_tax = new instance.web.Model("account.tax"); this.model_tax = new instance.web.Model("account.tax");
this.map_account_id_code = this.getParent().map_account_id_code; this.map_account_id_code = this.getParent().map_account_id_code;
this.map_tax_id_amount = this.getParent().map_tax_id_amount; this.map_tax_id_amount = this.getParent().map_tax_id_amount;
this.formatCurrency = this.getParent().formatCurrency;
this.presets = this.getParent().presets; this.presets = this.getParent().presets;
this.is_valid = true; this.is_valid = true;
this.is_consistent = true; // Used to prevent bad server requests this.is_consistent = true; // Used to prevent bad server requests
this.total_move_lines_num = undefined; // Used for pagers
this.filter = ""; this.filter = "";
// In rare cases like when deleting a statement line's partner we don't want the server to
// look for a reconciliation proposition (in this particular case it might find a move line
// matching the statement line and decide to set the statement line's partner accordingly)
this.do_load_reconciliation_proposition = true;
this.set("balance", undefined); // Debit is +, credit is -
this.on("change:balance", this, this.balanceChanged);
this.set("mode", undefined); this.set("mode", undefined);
this.on("change:mode", this, this.modeChanged); this.on("change:mode", this, this.modeChanged);
this.set("balance", undefined); // Debit is +, credit is -
this.on("change:balance", this, this.balanceChanged);
this.set("pager_index", 0); this.set("pager_index", 0);
this.on("change:pager_index", this, this.pagerChanged); this.on("change:pager_index", this, this.pagerChanged);
// NB : mv_lines represent the counterpart that will be created to reconcile existing move lines, so debit and credit are inverted // NB : mv_lines represent the counterpart that will be created to reconcile existing move lines, so debit and credit are inverted
this.set("mv_lines", []); this.set("mv_lines", []);
this.on("change:mv_lines", this, this.mvLinesChanged); this.on("change:mv_lines", this, this.mvLinesChanged);
this.mv_lines_deselected = []; // deselected lines are displayed on top of the match table
this.on("change:mv_lines_selected", this, this.mvLinesSelectedChanged); this.on("change:mv_lines_selected", this, this.mvLinesSelectedChanged);
this.set("lines_created", []); this.set("lines_created", []);
this.set("line_created_being_edited", [{'id': 0}]); this.set("line_created_being_edited", [{'id': 0}]);
this.on("change:lines_created", this, this.createdLinesChanged); this.on("change:lines_created", this, this.createdLinesChanged);
this.on("change:line_created_being_edited", this, this.createdLinesChanged); this.on("change:line_created_being_edited", this, this.createdLinesChanged);
//all lines associated to current reconciliation
this.propositions_lines = undefined;
}, },
start: function() { start: function() {
@ -516,49 +540,70 @@ openerp.account = function (instance) {
self.aestetic_animation_speed = 0; self.aestetic_animation_speed = 0;
self.is_consistent = false; self.is_consistent = false;
if (self.context.animate_entrance) self.$el.css("opacity", "0"); if (self.context.animate_entrance) {
self.$el.fadeOut(0);
// Fetch data self.$el.slideUp(0);
var deferred_fetch_data = new $.Deferred(); }
if (! self.context.initial_data_provided) { return $.when(self.loadData()).then(function(){
// Load statement line return $.when(self.render()).then(function(){
self.model_bank_statement_line self.is_consistent = true;
.call("get_statement_line_for_reconciliation", [self.st_line_id]) // Make an entrance
.then(function (data) { self.animation_speed = self.getParent().animation_speed;
self.st_line = data; self.aestetic_animation_speed = self.getParent().aestetic_animation_speed;
self.decorateStatementLine(self.st_line); if (self.context.animate_entrance) {
self.partner_id = data.partner_id; return self.$el.stop(true, true).fadeIn({ duration: self.aestetic_animation_speed, queue: false }).css('display', 'none').slideDown(self.aestetic_animation_speed);
$.when(self.loadReconciliationProposition()).then(function(){
deferred_fetch_data.resolve();
});
});
} else {
deferred_fetch_data.resolve();
} }
// Display the widget
return $.when(deferred_fetch_data).then(function(){
//load all lines that can be usefull for counterparts
var deferred_total_move_lines_num = self.model_bank_statement_line
.call("get_move_lines_counterparts_id", [self.st_line.id, []])
.then(function(lines){
_(lines).each(self.decorateMoveLine.bind(self));
self.propositions_lines = lines;
}); });
return deferred_total_move_lines_num; });
}).then(function(){ });
// Render template },
loadData: function() {
var self = this;
if (self.context.initial_data_provided)
return;
// Get ids of selected move lines (to exclude them from reconciliation proposition)
var excluded_move_lines_ids = [];
if (self.do_load_reconciliation_proposition) {
_.each(self.getParent().excluded_move_lines_ids, function(o){
excluded_move_lines_ids = excluded_move_lines_ids.concat(o);
});
}
// Load statement line
return self.model_bank_statement_line
.call("get_data_for_reconciliations", [[self.st_line_id], excluded_move_lines_ids, self.do_load_reconciliation_proposition])
.then(function (data) {
self.st_line = data[0].st_line;
self.decorateStatementLine(self.st_line);
self.partner_id = data[0].st_line.partner_id;
if (self.getParent().excluded_move_lines_ids[self.partner_id] === undefined)
self.getParent().excluded_move_lines_ids[self.partner_id] = [];
var mv_lines = [];
_.each(data[0].reconciliation_proposition, function(line) {
self.decorateMoveLine(line, self.st_line.currency_id);
mv_lines.push(line);
}, self);
self.set("mv_lines_selected", self.get("mv_lines_selected").concat(mv_lines));
});
},
render: function() {
var self = this;
var presets_array = []; var presets_array = [];
for (var id in self.presets) for (var id in self.presets)
if (self.presets.hasOwnProperty(id)) if (self.presets.hasOwnProperty(id))
presets_array.push(self.presets[id]); presets_array.push(self.presets[id]);
self.$el.prepend(QWeb.render("bank_statement_reconciliation_line", {line: self.st_line, mode: self.context.mode, presets: presets_array})); self.$el.prepend(QWeb.render("bank_statement_reconciliation_line", {
line: self.st_line,
mode: self.context.mode,
presets: presets_array
}));
// Stuff that require the template to be rendered // Stuff that require the template to be rendered
self.$(".match").slideUp(0); self.$(".match").slideUp(0);
self.$(".create").slideUp(0); self.$(".create").slideUp(0);
if (self.st_line.no_match) self.$el.addClass("no_match"); if (self.st_line.no_match) self.$el.addClass("no_match");
if (self.context.mode !== "match") self.render();
self.bindPopoverTo(self.$(".line_info_button")); self.bindPopoverTo(self.$(".line_info_button"));
self.createFormWidgets(); self.createFormWidgets();
// Special case hack : no identified partner // Special case hack : no identified partner
@ -566,32 +611,23 @@ openerp.account = function (instance) {
self.$el.css("opacity", "0"); self.$el.css("opacity", "0");
self.updateBalance(); self.updateBalance();
self.$(".change_partner_container").show(0); self.$(".change_partner_container").show(0);
self.change_partner_field.$el.find("input").attr("placeholder", _t("Select Partner"));
self.$(".match").slideUp(0); self.$(".match").slideUp(0);
self.$el.addClass("no_partner"); self.$el.addClass("no_partner");
self.set("mode", self.context.mode); self.set("mode", self.context.mode);
self.balanceChanged();
self.updateAccountingViewMatchedLines();
self.animation_speed = self.getParent().animation_speed; self.animation_speed = self.getParent().animation_speed;
self.aestetic_animation_speed = self.getParent().aestetic_animation_speed; self.aestetic_animation_speed = self.getParent().aestetic_animation_speed;
self.$el.animate({opacity: 1}, self.aestetic_animation_speed); self.$el.animate({opacity: 1}, self.aestetic_animation_speed);
self.is_consistent = true;
return; return;
} }
// TODO : the .on handler's returned deferred is lost // TODO : the .on handler's returned deferred is lost
return $.when(self.set("mode", self.context.mode)).then(function(){ return $.when(self.set("mode", self.context.mode)).then(function(){
self.is_consistent = true;
// Make sure the display is OK // Make sure the display is OK
self.balanceChanged(); self.balanceChanged();
self.createdLinesChanged(); self.createdLinesChanged();
self.updateAccountingViewMatchedLines(); self.updateAccountingViewMatchedLines();
// Make an entrance
self.animation_speed = self.getParent().animation_speed;
self.aestetic_animation_speed = self.getParent().aestetic_animation_speed;
if (self.context.animate_entrance) return self.$el.animate({opacity: 1}, self.aestetic_animation_speed);
});
});
}); });
}, },
@ -603,7 +639,7 @@ openerp.account = function (instance) {
_.each(self.getChildren(), function(o){ o.destroy() }); _.each(self.getChildren(), function(o){ o.destroy() });
self.is_consistent = false; self.is_consistent = false;
return $.when(self.$el.animate({opacity: 0}, self.animation_speed)).then(function() { return $.when(self.$el.animate({opacity: 0}, self.animation_speed)).then(function() {
self.getParent().unexcludeMoveLines(_.map(self.get("mv_lines_selected"), function(o){ return o.id })); self.getParent().unexcludeMoveLines(self, self.partner_id, _.map(self.get("mv_lines_selected"), function(o){ return o.id }));
$.each(self.$(".bootstrap_popover"), function(){ $(this).popover('destroy') }); $.each(self.$(".bootstrap_popover"), function(){ $(this).popover('destroy') });
self.$el.empty(); self.$el.empty();
self.$el.removeClass("no_partner"); self.$el.removeClass("no_partner");
@ -617,6 +653,7 @@ openerp.account = function (instance) {
self.set("pager_index", 0, {silent: true}); self.set("pager_index", 0, {silent: true});
self.set("mv_lines", [], {silent: true}); self.set("mv_lines", [], {silent: true});
self.set("mv_lines_selected", [], {silent: true}); self.set("mv_lines_selected", [], {silent: true});
self.mv_lines_deselected = [];
self.set("lines_created", [], {silent: true}); self.set("lines_created", [], {silent: true});
self.set("line_created_being_edited", [{'id': 0}], {silent: true}); self.set("line_created_being_edited", [{'id': 0}], {silent: true});
// Rebirth // Rebirth
@ -749,6 +786,7 @@ openerp.account = function (instance) {
self.change_partner_field.on("change:value", self.change_partner_field, function() { self.change_partner_field.on("change:value", self.change_partner_field, function() {
self.changePartner(this.get_value()); self.changePartner(this.get_value());
}); });
self.change_partner_field.$el.find("input").attr("placeholder", _t("Select Partner"));
field_manager.do_show(); field_manager.do_show();
}, },
@ -761,20 +799,19 @@ openerp.account = function (instance) {
}, },
// adds fields, prefixed with q_, to the move line for qweb rendering // adds fields, prefixed with q_, to the move line for qweb rendering
decorateMoveLine: function(line){ decorateMoveLine: function(line, currency_id) {
line.partial_reconcile = false; line.partial_reconcile = false;
line.propose_partial_reconcile = false; line.propose_partial_reconcile = false;
line['credit'] = [line['debit'], line['debit'] = line['credit']][0];
line.q_due_date = (line.date_maturity === false ? line.date : line.date_maturity); line.q_due_date = (line.date_maturity === false ? line.date : line.date_maturity);
line.q_amount = (line.debit !== 0 ? "- "+line.q_debit : "") + (line.credit !== 0 ? line.q_credit : ""); line.q_amount = (line.debit !== 0 ? "- "+line.q_debit : "") + (line.credit !== 0 ? line.q_credit : "");
line.q_popover = QWeb.render("bank_statement_reconciliation_move_line_details", {line: line});
line.q_label = line.name; line.q_label = line.name;
line.debit_str = this.formatCurrency(line.debit, currency_id);
if (line.has_no_partner){ line.credit_str = this.formatCurrency(line.credit, currency_id);
line.q_label = line.partner_name + ': ' +line.q_label; line.q_popover = QWeb.render("bank_statement_reconciliation_move_line_details", {line: line});
} if (line.has_no_partner)
// WARNING : pretty much of a ugly hack line.q_label = line.partner_name + ': ' + line.q_label;
// The value of account_move.ref is either the move's communication or it's name without the slashes if (line.ref && line.ref !== line.name)
if (line.ref && line.ref !== line.name.replace(/\//g,''))
line.q_label += " : " + line.ref; line.q_label += " : " + line.ref;
}, },
@ -819,31 +856,77 @@ openerp.account = function (instance) {
selectMoveLine: function(mv_line) { selectMoveLine: function(mv_line) {
var self = this; var self = this;
var line_id = mv_line.dataset.lineid; var line_id = mv_line.dataset.lineid;
var line = _.find(self.propositions_lines, function(o){ return o.id == line_id});
$(mv_line).attr('data-selected','true'); // find the line in mv_lines or mv_lines_deselected
self.getParent().excludeMoveLines([line_id]); var line = _.find(self.get("mv_lines"), function(o){ return o.id == line_id});
if (! line) {
line = _.find(self.mv_lines_deselected, function(o){ return o.id == line_id });
self.mv_lines_deselected = _.filter(self.mv_lines_deselected, function(o) { return o.id != line_id });
}
if (! line) return; // If no line found, we've got a syncing problem (let's turn a deaf ear)
// Warn the user if he's selecting lines from both a payable and a receivable account
var last_selected_line = _.last(self.get("mv_lines_selected"));
if (last_selected_line && last_selected_line.account_type != line.account_type) {
new instance.web.Dialog(this, {
title: _t("Warning"),
size: 'medium',
}, $("<div />").text(_.str.sprintf(_t("You are selecting transactions from both a payable and a receivable account.\n\nIn order to proceed, you first need to deselect the %s transactions."), last_selected_line.account_type))).open();
return;
}
// If statement line has no partner, give it the partner of the selected move line
if (!this.st_line.partner_id && line.partner_id) {
self.changePartner(line.partner_id, function() {
self.selectMoveLine(mv_line);
});
} else {
self.set("mv_lines_selected", self.get("mv_lines_selected").concat(line)); self.set("mv_lines_selected", self.get("mv_lines_selected").concat(line));
// $(mv_line).attr('data-selected','true');
// self.set("mv_lines_selected", self.get("mv_lines_selected").concat(line));
// this.set("mv_lines", _.reject(this.get("mv_lines"), function(o){return o.id == line_id}));
// this.getParent().excludeMoveLines([line_id]);
}
}, },
deselectMoveLine: function(mv_line) { deselectMoveLine: function(mv_line) {
var self = this; var self = this;
var line_id = mv_line.dataset.lineid; var line_id = mv_line.dataset.lineid;
var line = _.find(self.propositions_lines, function(o){ return o.id == line_id}); var line = _.find(self.get("mv_lines_selected"), function(o){ return o.id == line_id});
$(mv_line).attr('data-selected','false'); if (! line) return; // If no line found, we've got a syncing problem (let's turn a deaf ear)
self.getParent().unexcludeMoveLines([line_id]);
self.set("mv_lines_selected",_.filter(self.get("mv_lines_selected"), function(o) { return o.id != line_id })); // add the line to mv_lines_deselected and remove it from mv_lines_selected
self.mv_lines_deselected.unshift(line);
var mv_lines_selected = _.filter(self.get("mv_lines_selected"), function(o) { return o.id != line_id });
// remove partial reconciliation stuff if necessary
if (line.partial_reconcile === true) self.unpartialReconcileLine(line);
if (line.propose_partial_reconcile === true) line.propose_partial_reconcile = false;
self.$el.removeClass("no_match");
self.set("mode", "match");
self.set("mv_lines_selected", mv_lines_selected);
// $(mv_line).attr('data-selected','false');
// this.set("mv_lines", this.get("mv_lines").concat(line));
// this.getParent().unexcludeMoveLines([line_id]);
}, },
/** Matches pagination */ /** Matches pagination */
pagerControlLeftHandler: function() { pagerControlLeftHandler: function() {
var self = this; var self = this;
if (self.$(".pager_control_left").hasClass("disabled")) { return; /* shouldn't happen, anyway*/ }
if (self.total_move_lines_num < 0) { return; }
self.set("pager_index", self.get("pager_index")-1 ); self.set("pager_index", self.get("pager_index")-1 );
}, },
pagerControlRightHandler: function() { pagerControlRightHandler: function() {
var self = this; var self = this;
var new_index = self.get("pager_index")+1; var new_index = self.get("pager_index")+1;
if (self.$(".pager_control_right").hasClass("disabled")) { return; /* shouldn't happen, anyway*/ }
if ((new_index * self.max_move_lines_displayed) >= self.total_move_lines_num) { return; }
self.set("pager_index", new_index ); self.set("pager_index", new_index );
}, },
@ -851,9 +934,11 @@ openerp.account = function (instance) {
var self = this; var self = this;
self.set("pager_index", 0); self.set("pager_index", 0);
self.filter = self.$(".filter").val(); self.filter = self.$(".filter").val();
self.render(); window.clearTimeout(self.apply_filter_timeout);
self.apply_filter_timeout = window.setTimeout(self.proxy('updateMatches'), 200);
}, },
/** Creating */ /** Creating */
initializeCreateForm: function() { initializeCreateForm: function() {
@ -862,6 +947,7 @@ openerp.account = function (instance) {
_.each(self.create_form, function(field) { _.each(self.create_form, function(field) {
field.set("value", false); field.set("value", false);
}); });
self.label_field.set("value", self.st_line.name);
self.amount_field.set("value", -1*self.get("balance")); self.amount_field.set("value", -1*self.get("balance"));
self.account_id_field.focus(); self.account_id_field.focus();
}, },
@ -894,21 +980,26 @@ openerp.account = function (instance) {
var self = this; var self = this;
self.initializeCreateForm(); self.initializeCreateForm();
var preset = self.presets[e.currentTarget.dataset.presetid]; var preset = self.presets[e.currentTarget.dataset.presetid];
// Hack : set_value of a field calls a handler that returns a deferred because it could make a RPC call
// to compute the tax before it updates the line being edited. Unfortunately this deferred is lost.
// Hence this ugly hack to avoid concurrency problem that arose when setting amount (in initializeCreateForm), then tax, then another amount
if (preset.tax && self.tax_field) self.tax_field.set_value(false);
if (preset.amount && self.amount_field) self.amount_field.set_value(false);
for (var key in preset) { for (var key in preset) {
if (! preset.hasOwnProperty(key) || key === "amount") continue; if (! preset.hasOwnProperty(key) || key === "amount") continue;
if (self.hasOwnProperty(key+"_field")) if (preset[key] && self.hasOwnProperty(key+"_field"))
self[key+"_field"].set_value(preset[key]); self[key+"_field"].set_value(preset[key]);
} }
var sign = self.amount_field.get_value() < 0 ? -1 : 1;
if (preset.amount && self.amount_field) { if (preset.amount && self.amount_field) {
if (preset.amount_type === "fixed") if (preset.amount_type === "fixed")
self.amount_field.set_value(sign * preset.amount); self.amount_field.set_value(preset.amount);
else if (preset.amount_type === "percentage_of_total") else if (preset.amount_type === "percentage_of_total")
self.amount_field.set_value(sign * self.st_line.amount * preset.amount / 100); self.amount_field.set_value(self.st_line.amount * preset.amount / 100);
else if (preset.amount_type === "percentage_of_balance") { else if (preset.amount_type === "percentage_of_balance") {
self.amount_field.set_value(0); self.amount_field.set_value(0);
self.updateBalance(); self.updateBalance();
self.amount_field.set_value(sign * Math.abs(self.get("balance")) * preset.amount / 100); self.amount_field.set_value(-1 * self.get("balance") * preset.amount / 100);
} }
} }
}, },
@ -937,9 +1028,11 @@ openerp.account = function (instance) {
partnerNameClickHandler: function() { partnerNameClickHandler: function() {
var self = this; var self = this;
// Delete statement line's partner
return self.changePartner('', function() {
self.$(".partner_name").hide(); self.$(".partner_name").hide();
self.change_partner_field.$el.find("input").attr("placeholder", self.st_line.partner_name);
self.$(".change_partner_container").show(); self.$(".change_partner_container").show();
});
}, },
@ -985,34 +1078,37 @@ openerp.account = function (instance) {
table.empty(); table.empty();
var slice_start = self.get("pager_index") * self.max_move_lines_displayed; var slice_start = self.get("pager_index") * self.max_move_lines_displayed;
var slice_end = (self.get("pager_index")+1) * self.max_move_lines_displayed; var slice_end = (self.get("pager_index")+1) * self.max_move_lines_displayed;
_( _.filter(self.mv_lines_deselected, function(o){
var visible = 0 return o.name.indexOf(self.filter) !== -1 || o.ref.indexOf(self.filter) !== -1 })
_(self.get("mv_lines")).each(function(line){ .slice(slice_start, slice_end)).each(function(line){
if (visible >= slice_start && visible < slice_end) {
var $line = $(QWeb.render("bank_statement_reconciliation_move_line", {line: line, selected: false})); var $line = $(QWeb.render("bank_statement_reconciliation_move_line", {line: line, selected: false}));
self.bindPopoverTo($line.find(".line_info_button")); self.bindPopoverTo($line.find(".line_info_button"));
table.append($line); table.append($line);
nothing_displayed = false; nothing_displayed = false;
}
visible = visible + 1;
}); });
if (nothing_displayed) _(self.get("mv_lines")).each(function(line){
var $line = $(QWeb.render("bank_statement_reconciliation_move_line", {line: line, selected: false}));
self.bindPopoverTo($line.find(".line_info_button"));
table.append($line);
nothing_displayed = false;
});
if (nothing_displayed && this.filter !== "")
table.append(QWeb.render("filter_no_match", {filter_str: self.filter})); table.append(QWeb.render("filter_no_match", {filter_str: self.filter}));
}, },
updatePagerControls: function() { updatePagerControls: function() {
var self = this; var self = this;
if (self.get("pager_index") === 0) if (self.get("pager_index") === 0)
self.$(".pager_control_left").addClass("disabled"); self.$(".pager_control_left").addClass("disabled");
else else
self.$(".pager_control_left").removeClass("disabled"); self.$(".pager_control_left").removeClass("disabled");
if (self.get('mv_lines').length <= ((self.get("pager_index")+1) * self.max_move_lines_displayed)) if (self.total_move_lines_num <= ((self.get("pager_index")+1) * self.max_move_lines_displayed))
self.$(".pager_control_right").addClass("disabled"); self.$(".pager_control_right").addClass("disabled");
else else
self.$(".pager_control_right").removeClass("disabled"); self.$(".pager_control_right").removeClass("disabled");
}, },
/** Properties changed */ /** Properties changed */
// Updates the validation button and the "open balance" line // Updates the validation button and the "open balance" line
@ -1034,8 +1130,12 @@ openerp.account = function (instance) {
self.is_valid = false; self.is_valid = false;
var debit = (balance > 0 ? self.formatCurrency(balance, self.st_line.currency_id) : ""); var debit = (balance > 0 ? self.formatCurrency(balance, self.st_line.currency_id) : "");
var credit = (balance < 0 ? self.formatCurrency(-1*balance, self.st_line.currency_id) : ""); var credit = (balance < 0 ? self.formatCurrency(-1*balance, self.st_line.currency_id) : "");
var $line = $(QWeb.render("bank_statement_reconciliation_line_open_balance", {debit: debit, credit: credit, account_code: self.map_account_id_code[self.st_line.open_balance_account_id]})); var $line = $(QWeb.render("bank_statement_reconciliation_line_open_balance", {
$line.find('.js_open_balance')[0].innerHTML = "Choose counterpart"; debit: debit,
credit: credit,
account_code: self.map_account_id_code[self.st_line.open_balance_account_id]
}));
$line.find('.js_open_balance')[0].innerHTML = _t("Choose counterpart");
self.$(".tbody_open_balance").append($line); self.$(".tbody_open_balance").append($line);
} }
return; return;
@ -1049,7 +1149,11 @@ openerp.account = function (instance) {
self.$(".button_ok").text("Keep open"); self.$(".button_ok").text("Keep open");
var debit = (balance > 0 ? self.formatCurrency(balance, self.st_line.currency_id) : ""); var debit = (balance > 0 ? self.formatCurrency(balance, self.st_line.currency_id) : "");
var credit = (balance < 0 ? self.formatCurrency(-1*balance, self.st_line.currency_id) : ""); var credit = (balance < 0 ? self.formatCurrency(-1*balance, self.st_line.currency_id) : "");
var $line = $(QWeb.render("bank_statement_reconciliation_line_open_balance", {debit: debit, credit: credit, account_code: self.map_account_id_code[self.st_line.open_balance_account_id]})); var $line = $(QWeb.render("bank_statement_reconciliation_line_open_balance", {
debit: debit,
credit: credit,
account_code: self.map_account_id_code[self.st_line.open_balance_account_id]
}));
self.$(".tbody_open_balance").append($line); self.$(".tbody_open_balance").append($line);
} }
}, },
@ -1059,15 +1163,21 @@ openerp.account = function (instance) {
self.$(".action_pane.active").removeClass("active"); self.$(".action_pane.active").removeClass("active");
// Special case hack : if no_partner and mode == inactive // Special case hack : if no_partner, either inactive or create
if (self.st_line.has_no_partner) { if (self.st_line.has_no_partner) {
if (self.get("mode") === "inactive") { if (self.get("mode") === "inactive") {
self.$(".match").slideUp(self.animation_speed); self.$(".match").slideUp(self.animation_speed);
self.$(".create").slideUp(self.animation_speed); self.$(".create").slideUp(self.animation_speed);
self.$(".toggle_match").removeClass("visible_toggle"); self.$(".toggle_match").removeClass("visible_toggle");
self.el.dataset.mode = "inactive"; self.el.dataset.mode = "inactive";
return; } else {
self.initializeCreateForm();
self.$(".match").slideUp(self.animation_speed);
self.$(".create").slideDown(self.animation_speed);
self.$(".toggle_match").addClass("visible_toggle");
self.el.dataset.mode = "create";
} }
return;
} }
if (self.get("mode") === "inactive") { if (self.get("mode") === "inactive") {
@ -1076,7 +1186,7 @@ openerp.account = function (instance) {
self.el.dataset.mode = "inactive"; self.el.dataset.mode = "inactive";
} else if (self.get("mode") === "match") { } else if (self.get("mode") === "match") {
return $.when(self.render()).then(function() { return $.when(self.updateMatches()).then(function() {
if (self.$el.hasClass("no_match")) { if (self.$el.hasClass("no_match")) {
self.set("mode", "inactive"); self.set("mode", "inactive");
return; return;
@ -1095,15 +1205,18 @@ openerp.account = function (instance) {
}, },
pagerChanged: function() { pagerChanged: function() {
var self = this; this.updateMatches();
self.render();
}, },
mvLinesChanged: function() { mvLinesChanged: function() {
var self = this; var self = this;
// If pager_index is out of range, set it to display the last page
if (self.get("pager_index") !== 0 && self.total_move_lines_num <= (self.get("pager_index") * self.max_move_lines_displayed)) {
self.set("pager_index", Math.ceil(self.total_move_lines_num/self.max_move_lines_displayed)-1);
}
// If there is no match to display, disable match view and pass in mode inactive // If there is no match to display, disable match view and pass in mode inactive
if (self.get("mv_lines").length === 0 && self.filter === "") { if (self.total_move_lines_num + self.mv_lines_deselected.length === 0 && self.filter === "") {
self.$el.addClass("no_match"); self.$el.addClass("no_match");
if (self.get("mode") === "match") { if (self.get("mode") === "match") {
self.set("mode", "inactive"); self.set("mode", "inactive");
@ -1122,11 +1235,13 @@ openerp.account = function (instance) {
var added_lines_ids = _.map(_.difference(val.newValue, val.oldValue), function(o){ return o.id }); var added_lines_ids = _.map(_.difference(val.newValue, val.oldValue), function(o){ return o.id });
var removed_lines_ids = _.map(_.difference(val.oldValue, val.newValue), function(o){ return o.id }); var removed_lines_ids = _.map(_.difference(val.oldValue, val.newValue), function(o){ return o.id });
self.getParent().excludeMoveLines(added_lines_ids); self.getParent().excludeMoveLines(self, self.partner_id, added_lines_ids);
self.getParent().unexcludeMoveLines(removed_lines_ids); self.getParent().unexcludeMoveLines(self, self.partner_id, removed_lines_ids);
$.when(self.updateMatches()).then(function(){
self.updateAccountingViewMatchedLines(); self.updateAccountingViewMatchedLines();
self.updateBalance(); self.updateBalance();
});
}, },
// Generic function for updating the line_created_being_edited // Generic function for updating the line_created_being_edited
@ -1134,7 +1249,6 @@ openerp.account = function (instance) {
var self = this; var self = this;
var line_created_being_edited = self.get("line_created_being_edited"); var line_created_being_edited = self.get("line_created_being_edited");
line_created_being_edited[0][elt.corresponding_property] = val.newValue; line_created_being_edited[0][elt.corresponding_property] = val.newValue;
line_created_being_edited[0].currency_id = self.st_line.currency_id; line_created_being_edited[0].currency_id = self.st_line.currency_id;
// Specific cases // Specific cases
@ -1155,17 +1269,26 @@ openerp.account = function (instance) {
var current_line_cursor = 1; var current_line_cursor = 1;
$.each(data.taxes, function(index, tax){ $.each(data.taxes, function(index, tax){
if (tax.amount !== 0.0) { if (tax.amount !== 0.0) {
var tax_account_id = (amount > 0 ? tax.account_collected_id : tax.account_paid_id) var tax_account_id = (amount > 0 ? tax.account_collected_id : tax.account_paid_id);
tax_account_id = tax_account_id !== false ? tax_account_id: line_created_being_edited[0].account_id tax_account_id = tax_account_id !== false ? tax_account_id: line_created_being_edited[0].account_id;
line_created_being_edited[current_line_cursor] = {id: line_created_being_edited[0].id, account_id: tax_account_id, account_num: self.map_account_id_code[tax_account_id], label: tax.name, amount: tax.amount, no_remove_action: true, currency_id: self.st_line.currency_id, is_tax_line: true}; line_created_being_edited[current_line_cursor] = {
current_line_cursor = current_line_cursor + 1; id: line_created_being_edited[0].id,
account_id: tax_account_id,
account_num: self.map_account_id_code[tax_account_id],
label: tax.name,
amount: tax.amount,
no_remove_action: true,
currency_id: self.st_line.currency_id,
is_tax_line: true
}; };
current_line_cursor = current_line_cursor + 1;
}
}); });
} }
); );
} else { } else {
line_created_being_edited[0].amount = amount; line_created_being_edited[0].amount = amount;
delete line_created_being_edited[1]; line_created_being_edited.length = 1;
deferred_tax.resolve(); deferred_tax.resolve();
} }
} else { deferred_tax.resolve(); } } else { deferred_tax.resolve(); }
@ -1211,7 +1334,7 @@ openerp.account = function (instance) {
var balance = self.get("balance"); var balance = self.get("balance");
line.initial_amount = line.debit !== 0 ? line.debit : -1 * line.credit; line.initial_amount = line.debit !== 0 ? line.debit : -1 * line.credit;
if (balance < 0) { if (balance < 0) {
line.debit -= balance; line.debit += balance;
line.debit_str = self.formatCurrency(line.debit, self.st_line.currency_id); line.debit_str = self.formatCurrency(line.debit, self.st_line.currency_id);
} else { } else {
line.credit -= balance; line.credit -= balance;
@ -1250,6 +1373,19 @@ openerp.account = function (instance) {
updateBalance: function() { updateBalance: function() {
var self = this; var self = this;
var mv_lines_selected = self.get("mv_lines_selected"); var mv_lines_selected = self.get("mv_lines_selected");
var lines_selected_num = mv_lines_selected.length;
var lines_created_num = self.getCreatedLines().length;
// Undo partial reconciliation if necessary
if (lines_selected_num !== 1 || lines_created_num !== 0) {
_.each(mv_lines_selected, function(line) {
if (line.partial_reconcile === true) self.unpartialReconcileLine(line);
if (line.propose_partial_reconcile === true) line.propose_partial_reconcile = false;
});
self.updateAccountingViewMatchedLines();
}
// Compute balance
var balance = 0; var balance = 0;
balance -= self.st_line.amount; balance -= self.st_line.amount;
_.each(mv_lines_selected, function(o) { _.each(mv_lines_selected, function(o) {
@ -1258,57 +1394,71 @@ openerp.account = function (instance) {
_.each(self.getCreatedLines(), function(o) { _.each(self.getCreatedLines(), function(o) {
balance += o.amount; balance += o.amount;
}); });
// Should work as long as currency's rounding factor is > 0.001 (ie: don't use gold kilos as a currency)
balance = Math.round(balance*1000)/1000;
self.set("balance", balance); self.set("balance", balance);
// Propose partial reconciliation if necessary // Propose partial reconciliation if necessary
var lines_selected_num = mv_lines_selected.length;
var lines_created_num = self.getCreatedLines().length;
if (lines_selected_num === 1 && lines_created_num === 0 && self.st_line.amount * balance > 0) { if (lines_selected_num === 1 && lines_created_num === 0 && self.st_line.amount * balance > 0) {
mv_lines_selected[0].propose_partial_reconcile = true; mv_lines_selected[0].propose_partial_reconcile = true;
self.updateAccountingViewMatchedLines(); self.updateAccountingViewMatchedLines();
} }
if (lines_selected_num !== 1 || lines_created_num !== 0) {
// remove partial reconciliation stuff if necessary
_.each(mv_lines_selected, function(line) {
if (line.partial_reconcile === true) self.unpartialReconcileLine(line);
if (line.propose_partial_reconcile === true) line.propose_partial_reconcile = false;
});
self.updateAccountingViewMatchedLines();
}
}, },
loadReconciliationProposition: function() { // Loads move lines according to the widget's state
updateMatches: function() {
if (this.st_line.has_no_partner) return;
var self = this; var self = this;
return self.model_bank_statement_line var deselected_lines_num = self.mv_lines_deselected.length;
.call("get_reconciliation_proposition", [self.st_line.id, self.getParent().excluded_move_lines_ids]) var move_lines_num = 0;
var offset = self.get("pager_index") * self.max_move_lines_displayed - deselected_lines_num;
if (offset < 0) offset = 0;
var limit = (self.get("pager_index")+1) * self.max_move_lines_displayed - deselected_lines_num;
if (limit > self.max_move_lines_displayed) limit = self.max_move_lines_displayed;
var excluded_ids = _.collect(self.get("mv_lines_selected").concat(self.mv_lines_deselected), function(o){ return o.id });
excluded_ids = excluded_ids.concat(self.getParent().excluded_move_lines_ids[self.partner_id]);
var deferred_move_lines;
var move_lines = [];
if (limit > 0) {
// Load move lines
deferred_move_lines = self.model_bank_statement_line
.call("get_move_lines_for_reconciliation_by_statement_line_id", [self.st_line.id, excluded_ids, self.filter, offset, limit])
.then(function (lines) { .then(function (lines) {
_(lines).each(self.decorateMoveLine.bind(self)); _.each(lines, function(line) {
self.set("mv_lines_selected", self.get("mv_lines_selected").concat(lines)); self.decorateMoveLine(line, self.st_line.currency_id);
move_lines.push(line);
}, self);
}); });
},
render: function() {
var self = this;
var lines_to_show = [];
_.each(self.propositions_lines, function(line){
var filter = (line.q_label.toLowerCase().indexOf(self.filter.toLowerCase()) > -1 || line.account_code.toLowerCase().indexOf(self.filter.toLowerCase()) > -1);
if (self.getParent().excluded_move_lines_ids.indexOf(line.id) === -1 && filter) {
lines_to_show.push(line);
} }
// Fetch the number of move lines corresponding to this statement line and this filter
var deferred_total_move_lines_num = self.model_bank_statement_line
.call("get_move_lines_for_reconciliation_by_statement_line_id", [self.st_line.id, excluded_ids, self.filter, 0, undefined, true])
.then(function(num){
move_lines_num = num;
});
return $.when(deferred_move_lines, deferred_total_move_lines_num).then(function(){
self.total_move_lines_num = move_lines_num + deselected_lines_num;
self.set("mv_lines", move_lines);
}); });
self.set("mv_lines", lines_to_show);
}, },
// Changes the partner_id of the statement_line in the DB and reloads the widget // Changes the partner_id of the statement_line in the DB and reloads the widget
changePartner: function(partner_id) { changePartner: function(partner_id, callback) {
var self = this; var self = this;
self.is_consistent = false; self.is_consistent = false;
return self.model_bank_statement_line return self.model_bank_statement_line
// Update model // Update model
.call("write", [[self.st_line_id], {'partner_id': partner_id}]) .call("write", [[self.st_line_id], {'partner_id': partner_id}])
.then(function () { .then(function () {
self.do_load_reconciliation_proposition = false; // of the server might set the statement line's partner
return $.when(self.restart(self.get("mode"))).then(function(){ return $.when(self.restart(self.get("mode"))).then(function(){
self.do_load_reconciliation_proposition = true;
self.is_consistent = true; self.is_consistent = true;
self.set("mode", "match");
if (callback) callback();
}); });
}); });
}, },
@ -1353,15 +1503,12 @@ openerp.account = function (instance) {
}, },
// Persist data, notify parent view and terminate widget // Persist data, notify parent view and terminate widget
persistAndDestroy: function() { persistAndDestroy: function(speed) {
var self = this; var self = this;
speed = (isNaN(speed) ? self.animation_speed : speed);
if (! self.is_consistent) return; if (! self.is_consistent) return;
// Prepare data self.getParent().unexcludeMoveLines(self, self.partner_id, _.map(self.get("mv_lines_selected"), function(o){ return o.id }));
var mv_line_dicts = [];
_.each(self.get("mv_lines_selected"), function(o) { mv_line_dicts.push(self.prepareSelectedMoveLineForPersisting(o)) });
_.each(self.getCreatedLines(), function(o) { mv_line_dicts.push(self.prepareCreatedMoveLineForPersisting(o)) });
if (Math.abs(self.get("balance")).toFixed(3) !== "0.000") mv_line_dicts.push(self.prepareOpenBalanceForPersisting());
// Sliding animation // Sliding animation
var height = self.$el.outerHeight(); var height = self.$el.outerHeight();
@ -1370,11 +1517,10 @@ openerp.account = function (instance) {
.css("marginTop", self.$el.css("marginTop")) .css("marginTop", self.$el.css("marginTop"))
.css("marginBottom", self.$el.css("marginBottom")); .css("marginBottom", self.$el.css("marginBottom"));
self.$el.wrap(container); self.$el.wrap(container);
var deferred_animation = self.$el.parent().slideUp(self.animation_speed*height/150); var deferred_animation = self.$el.parent().slideUp(speed*height/150);
// RPC // RPC
return self.model_bank_statement_line return $.when(self.makeRPCForPersisting())
.call("process_reconciliation", [self.st_line_id, mv_line_dicts])
.then(function () { .then(function () {
$.each(self.$(".bootstrap_popover"), function(){ $(this).popover('destroy') }); $.each(self.$(".bootstrap_popover"), function(){ $(this).popover('destroy') });
return $.when(deferred_animation).then(function(){ return $.when(deferred_animation).then(function(){
@ -1385,11 +1531,20 @@ openerp.account = function (instance) {
}); });
}); });
}, function(){ }, function(){
self.$el.parent().slideDown(self.animation_speed*height/150, function(){ self.$el.parent().slideDown(speed*height/150, function(){
self.$el.unwrap(); self.$el.unwrap();
}); });
}); });
},
makeRPCForPersisting: function() {
var self = this;
var mv_line_dicts = [];
_.each(self.get("mv_lines_selected"), function(o) { mv_line_dicts.push(self.prepareSelectedMoveLineForPersisting(o)) });
_.each(self.getCreatedLines(), function(o) { mv_line_dicts.push(self.prepareCreatedMoveLineForPersisting(o)) });
if (Math.abs(self.get("balance")).toFixed(3) !== "0.000") mv_line_dicts.push(self.prepareOpenBalanceForPersisting());
return self.model_bank_statement_line
.call("process_reconciliation", [self.st_line_id, mv_line_dicts]);
}, },
}); });

View File

@ -53,22 +53,21 @@
<table class="accounting_view"> <table class="accounting_view">
<caption> <caption>
<button class="button_ok"></button> <button class="button_ok"></button>
<span class="partner_name"><t t-if="line.partner_id"><t t-esc="line.partner_name"/></t></span> <span t-if="! line.has_no_partner" class="partner_name"><t t-esc="line.partner_name"/></span>
<div class="change_partner_container oe_form"></div> <div class="change_partner_container oe_form"></div>
</caption> </caption>
<tbody class="tbody_initial_line"> <tbody class="tbody_initial_line">
<tr class="initial_line"> <tr class="initial_line">
<td><span class="toggle_match glyphicon glyphicon-cog"></span></td> <td class="cell_action"><span class="toggle_match glyphicon glyphicon-cog"></span></td>
<td><t t-esc="line.account_code"/></td> <td class="cell_account_code"><t t-esc="line.account_code"/></td>
<td><t t-esc="line.date"/></td> <td class="cell_due_date"><t t-esc="line.date"/></td>
<td><t t-if="!line.partner_id"><t t-if="line.partner_name"><t t-esc="line.partner_name"/>: </t></t> <td class="cell_label"><t t-if="line.name" t-esc="line.name"/>
<t t-esc="line.name"/>
<t t-if="line.amount_currency_str"> (<t t-esc="line.amount_currency_str"/>)</t></td> <t t-if="line.amount_currency_str"> (<t t-esc="line.amount_currency_str"/>)</t></td>
<td><t t-if="line.amount &gt; 0"> <td class="cell_debit"><t t-if="line.amount &gt; 0">
<t t-esc="line.amount_str"/></t></td> <t t-esc="line.amount_str"/></t></td>
<td><t t-if="line.amount &lt; 0"> <td class="cell_credit"><t t-if="line.amount &lt; 0">
<t t-esc="line.amount_str"/></t></td> <t t-esc="line.amount_str"/></t></td>
<td><span class="line_info_button glyphicon glyphicon-info-sign" t-att-data-content="line.q_popover"></span></td> <td class="cell_info_popover"><span class="line_info_button glyphicon glyphicon-info-sign" t-att-data-content="line.q_popover"></span></td>
</tr> </tr>
</tbody> </tbody>
<tbody class="tbody_matched_lines"></tbody> <tbody class="tbody_matched_lines"></tbody>
@ -115,7 +114,7 @@
<t t-name="bank_statement_reconciliation_line_details"> <t t-name="bank_statement_reconciliation_line_details">
<table class='details'> <table class='details'>
<tr><td>Date</td><td><t t-esc="line.date"/></td></tr> <tr><td>Date</td><td><t t-esc="line.date"/></td></tr>
<tr><td>Partner</td><td><t t-esc="line.partner_name"/></td></tr> <tr t-if="line.partner_name"><td>Partner</td><td><t t-esc="line.partner_name"/></td></tr>
<tr t-if="line.ref"><td>Transaction</td><td><t t-esc="line.ref"/></td></tr> <tr t-if="line.ref"><td>Transaction</td><td><t t-esc="line.ref"/></td></tr>
<tr><td>Description</td><td><t t-esc="line.name"/></td></tr> <tr><td>Description</td><td><t t-esc="line.name"/></td></tr>
<tr><td>Amount</td><td><t t-esc="line.amount_str"/><t t-if="line.amount_currency_str"> (<t t-esc="line.amount_currency_str"/>)</t></td></tr> <tr><td>Amount</td><td><t t-esc="line.amount_str"/><t t-if="line.amount_currency_str"> (<t t-esc="line.amount_currency_str"/>)</t></td></tr>
@ -123,40 +122,26 @@
</table> </table>
</t> </t>
<t t-name="bank_statement_reconciliation_move_line"> <t t-name="bank_statement_reconciliation_move_line">
<tr class="mv_line" t-att-data-lineid="line.id" t-att-data-selected="selected"> <tr class="mv_line" t-att-data-lineid="line.id" t-att-data-selected="selected">
<td><span class="glyphicon glyphicon-add-remove"></span></td> <td class="cell_action"><span class="glyphicon glyphicon-add-remove"></span></td>
<td><t t-esc="line.account_code"/></td> <td class="cell_account_code" t-if="line.account_code"><t t-esc="line.account_code"/></td>
<td><t t-esc="line.q_due_date"/></td> <td class="cell_due_date"><t t-esc="line.q_due_date"/></td>
<td class="js_qlabel"><t t-esc="line.q_label"/></td> <td class="cell_label js_qlabel"><t t-esc="line.q_label"/></td>
<td class="cell_debit"><t t-if="line.debit !== 0"><t t-if="line.propose_partial_reconcile" t-call="icon_do_partial_reconciliation"></t><t t-if="line.partial_reconcile" t-call="icon_undo_partial_reconciliation"></t><t t-esc="line.debit_str"/></t></td>
<td><t t-if="line.debit !== 0"> <td class="cell_credit"><t t-if="line.credit !== 0"><t t-if="line.propose_partial_reconcile"><t t-call="icon_do_partial_reconciliation" /></t><t t-if="line.partial_reconcile"><t t-call="icon_undo_partial_reconciliation" /></t><t t-esc="line.credit_str"/></t></td>
<t t-if="line.propose_partial_reconcile" t-call="icon_do_partial_reconciliation"></t> <td class="cell_info_popover"><span class="line_info_button glyphicon glyphicon-info-sign" t-att-data-content="line.q_popover"></span></td>
<t t-if="line.partial_reconcile" t-call="icon_undo_partial_reconciliation"></t>
<t t-esc="line.debit_str"/>
</t></td>
<td><t t-if="line.credit !== 0">
<t t-if="line.propose_partial_reconcile"><t t-call="icon_do_partial_reconciliation" /></t>
<t t-if="line.partial_reconcile"><t t-call="icon_undo_partial_reconciliation" /></t>
<t t-esc="line.credit_str"/>
</t></td>
<td><span class="line_info_button glyphicon glyphicon-info-sign" t-att-data-content="line.q_popover"></span></td>
</tr> </tr>
</t> </t>
<t t-name="icon_do_partial_reconciliation"> <t t-name="icon_do_partial_reconciliation"><i class="do_partial_reconcile_button fa fa-exclamation-triangle" data-content="This move's amount is higher than the transaction's amount. Click to do a partial reconciliation"></i></t>
<i class="do_partial_reconcile_button fa fa-exclamation-triangle" data-content="This move's amount is higher than the transaction's amount. Click to do a partial reconciliation"></i>
</t>
<t t-name="icon_undo_partial_reconciliation"> <t t-name="icon_undo_partial_reconciliation"><i class="undo_partial_reconcile_button fa fa-exclamation-triangle" data-content="Undo the partial reconciliation."></i></t>
<i class="undo_partial_reconcile_button fa fa-exclamation-triangle" data-content="Undo the partial reconciliation."></i>
</t>
<t t-name="bank_statement_reconciliation_move_line_details"> <t t-name="bank_statement_reconciliation_move_line_details">
<table class='details'> <table class='details'>
<tr><td>ID</td><td><t t-esc="line.id"/></td></tr> <tr><td>id</td><td><t t-esc="line.id" /></td></tr>
<tr><td>Account</td><td><t t-esc="line.account_code"/> <t t-esc="line.account_name"/></td></tr> <tr t-if="line.account_code"><td>Account</td><td><t t-esc="line.account_code"/> <t t-esc="line.account_name"/></td></tr>
<tr><td>Journal</td><td><t t-esc="line.journal_name"/></td></tr> <tr><td>Journal</td><td><t t-esc="line.journal_name"/></td></tr>
<tr><td>Period</td><td><t t-esc="line.period_name"/></td></tr> <tr><td>Period</td><td><t t-esc="line.period_name"/></td></tr>
<tr><td>Date</td><td><t t-esc="line.date"/></td></tr> <tr><td>Date</td><td><t t-esc="line.date"/></td></tr>
@ -168,28 +153,27 @@
</table> </table>
</t> </t>
<t t-name="bank_statement_reconciliation_created_line"> <t t-name="bank_statement_reconciliation_created_line">
<tr class="created_line" t-att-data-lineid="line.id"> <tr class="created_line" t-att-data-lineid="line.id">
<td><t t-if="! line.no_remove_action"><span class="line_remove_button glyphicon glyphicon-remove"></span></t></td> <td class="cell_action"><t t-if="! line.no_remove_action"><span class="line_remove_button glyphicon glyphicon-remove"></span></t></td>
<td><t t-esc="line.account_num"/></td> <td class="cell_account_code"><t t-esc="line.account_num"/></td>
<td></td> <td class="cell_due_date"></td>
<td><t t-esc="line.label"/></td> <td class="cell_label"><t t-esc="line.label"/></td>
<td><t t-if="line.amount &lt; 0"><t t-esc="line.amount_str"/></t></td> <td class="cell_debit"><t t-if="line.amount &lt; 0"><t t-esc="line.amount_str"/></t></td>
<td><t t-if="line.amount &gt; 0"><t t-esc="line.amount_str"/></t></td> <td class="cell_credit"><t t-if="line.amount &gt; 0"><t t-esc="line.amount_str"/></t></td>
<td></td> <td class="cell_info_popover"></td>
</tr> </tr>
</t> </t>
<t t-name="bank_statement_reconciliation_line_open_balance"> <t t-name="bank_statement_reconciliation_line_open_balance">
<tr class="line_open_balance"> <tr class="line_open_balance">
<td><span class="toggle_create glyphicon glyphicon-play"></span></td> <td class="cell_action"><span class="toggle_create glyphicon glyphicon-play"></span></td>
<td><t t-esc="account_code"/></td> <td class="cell_account_code"><t t-esc="account_code"/></td>
<td></td> <td class="cell_due_date"></td>
<td class="js_open_balance">Open balance</td> <td class="cell_label js_open_balance">Open balance</td>
<td><t t-esc="debit"/></td> <td class="cell_debit"><t t-esc="debit"/></td>
<td><t t-esc="credit"/></td> <td class="cell_credit"><t t-esc="credit"/></td>
<td></td> <td class="cell_info_popover"></td>
</tr> </tr>
</t> </t>
</templates> </templates>

View File

@ -1,7 +1,9 @@
from . import test_tax from . import test_tax
from . import test_search from . import test_search
from . import test_reconciliation
fast_suite = [ fast_suite = [
test_tax, test_tax,
test_search, test_search,
test_reconciliation,
] ]

View File

@ -0,0 +1,8 @@
import openerp.tests
@openerp.tests.common.at_install(False)
@openerp.tests.common.post_install(True)
class TestUi(openerp.tests.HttpCase):
def test_01_admin_bank_statement_reconciliation(self):
self.phantom_js("/", "openerp.Tour.run('bank_statement_reconciliation', 'test')", "openerp.Tour.tours.bank_statement_reconciliation", login="admin")

View File

@ -0,0 +1,162 @@
from openerp.tests.common import TransactionCase
class TestReconciliation(TransactionCase):
"""Tests for reconciliation (account.tax)
Test used to check that when doing a sale or purchase invoice in a different currency,
the result will be balanced.
"""
def setUp(self):
super(TestReconciliation, self).setUp()
self.account_invoice_model = self.registry('account.invoice')
self.account_invoice_line_model = self.registry('account.invoice.line')
self.acc_bank_stmt_model = self.registry('account.bank.statement')
self.acc_bank_stmt_line_model = self.registry('account.bank.statement.line')
self.partner_agrolait_id = self.registry("ir.model.data").get_object_reference(self.cr, self.uid, "base", "res_partner_2")[1]
self.currency_swiss_id = self.registry("ir.model.data").get_object_reference(self.cr, self.uid, "base", "CHF")[1]
self.currency_usd_id = self.registry("ir.model.data").get_object_reference(self.cr, self.uid, "base", "USD")[1]
self.account_rcv_id = self.registry("ir.model.data").get_object_reference(self.cr, self.uid, "account", "a_recv")[1]
self.account_rsa_id = self.registry("ir.model.data").get_object_reference(self.cr, self.uid, "account", "rsa")[1]
self.product_id = self.registry("ir.model.data").get_object_reference(self.cr, self.uid, "product", "product_product_4")[1]
self.bank_journal_usd_id = self.registry("ir.model.data").get_object_reference(self.cr, self.uid, "account", "bank_journal_usd")[1]
self.account_usd_id = self.registry("ir.model.data").get_object_reference(self.cr, self.uid, "account", "usd_bnk")[1]
self.company_id = self.registry("ir.model.data").get_object_reference(self.cr, self.uid, "base", "main_company")[1]
#set expense_currency_exchange_account_id and income_currency_exchange_account_id to a random account
self.registry("res.company").write(self.cr, self.uid, [self.company_id], {'expense_currency_exchange_account_id': self.account_rsa_id, 'income_currency_exchange_account_id':self.account_rsa_id})
def test_balanced_customer_invoice(self):
cr, uid = self.cr, self.uid
#we create an invoice in CHF
invoice_id = self.account_invoice_model.create(cr, uid, {'partner_id': self.partner_agrolait_id,
'reference_type': 'none',
'currency_id': self.currency_swiss_id,
'name': 'invoice to client',
'account_id': self.account_rcv_id,
'type': 'out_invoice'
})
self.account_invoice_line_model.create(cr, uid, {'product_id': self.product_id,
'quantity': 1,
'price_unit': 100,
'invoice_id': invoice_id,
'name': 'product that cost 100',})
#validate purchase
self.registry('account.invoice').signal_workflow(cr, uid, [invoice_id], 'invoice_open')
invoice_record = self.account_invoice_model.browse(cr, uid, [invoice_id])
#we pay half of it on a journal with currency in dollar (bank statement)
bank_stmt_id = self.acc_bank_stmt_model.create(cr, uid, {'journal_id': self.bank_journal_usd_id,})
bank_stmt_line_id = self.acc_bank_stmt_line_model.create(cr, uid, {'name': 'half payment',
'statement_id': bank_stmt_id,
'partner_id': self.partner_agrolait_id,
'amount': 42,
'amount_currency': 50,
'currency_id': self.currency_swiss_id,})
#reconcile the payment with the invoice
for l in invoice_record.move_id.line_id:
if l.account_id.id == self.account_rcv_id:
line_id = l
break
self.acc_bank_stmt_line_model.process_reconciliation(cr, uid, bank_stmt_line_id, [
{'counterpart_move_line_id': line_id.id, 'credit':50, 'debit':0, 'name': line_id.name,}])
#we check that the line is balanced (bank statement line)
move_line_ids = self.acc_bank_stmt_model.browse(cr,uid,bank_stmt_id).move_line_ids
self.assertEquals(len(move_line_ids), 3)
checked_line = 0
for move_line in move_line_ids:
if move_line.account_id.id == self.account_usd_id:
self.assertEquals(move_line.debit, 27.47)
self.assertEquals(move_line.credit, 0.0)
self.assertEquals(move_line.amount_currency, 42)
self.assertEquals(move_line.currency_id.id, self.currency_usd_id)
checked_line += 1
continue
if move_line.account_id.id == self.account_rcv_id:
self.assertEquals(move_line.debit, 0.0)
self.assertEquals(move_line.credit, 38.21)
self.assertEquals(move_line.amount_currency, -50)
self.assertEquals(move_line.currency_id.id, self.currency_swiss_id)
checked_line += 1
continue
if move_line.account_id.id == self.account_rsa_id:
self.assertEquals(move_line.debit, 10.74)
self.assertEquals(move_line.credit, 0.0)
checked_line += 1
continue
self.assertEquals(checked_line, 3)
def test_balanced_supplier_invoice(self):
cr, uid = self.cr, self.uid
#we create a supplier invoice in CHF
invoice_id = self.account_invoice_model.create(cr, uid, {'partner_id': self.partner_agrolait_id,
'reference_type': 'none',
'currency_id': self.currency_swiss_id,
'name': 'invoice to client',
'account_id': self.account_rcv_id,
'type': 'in_invoice'
})
self.account_invoice_line_model.create(cr, uid, {'product_id': self.product_id,
'quantity': 1,
'price_unit': 100,
'invoice_id': invoice_id,
'name': 'product that cost 100',})
#validate purchase
self.registry('account.invoice').signal_workflow(cr, uid, [invoice_id], 'invoice_open')
invoice_record = self.account_invoice_model.browse(cr, uid, [invoice_id])
#we pay half of it on a journal with currency in dollar (bank statement)
bank_stmt_id = self.acc_bank_stmt_model.create(cr, uid, {'journal_id': self.bank_journal_usd_id,})
bank_stmt_line_id = self.acc_bank_stmt_line_model.create(cr, uid, {'name': 'half payment',
'statement_id': bank_stmt_id,
'partner_id': self.partner_agrolait_id,
'amount': -42,
'amount_currency': -50,
'currency_id': self.currency_swiss_id,})
#reconcile the payment with the invoice
for l in invoice_record.move_id.line_id:
if l.account_id.id == self.account_rcv_id:
line_id = l
break
self.acc_bank_stmt_line_model.process_reconciliation(cr, uid, bank_stmt_line_id, [
{'counterpart_move_line_id': line_id.id, 'credit':0, 'debit':50, 'name': line_id.name,}])
#we check that the line is balanced (bank statement line)
move_line_ids = self.acc_bank_stmt_model.browse(cr,uid,bank_stmt_id).move_line_ids
self.assertEquals(len(move_line_ids), 3)
checked_line = 0
for move_line in move_line_ids:
if move_line.account_id.id == self.account_usd_id:
self.assertEquals(move_line.debit, 0.0)
self.assertEquals(move_line.credit, 27.47)
self.assertEquals(move_line.amount_currency, -42)
self.assertEquals(move_line.currency_id.id, self.currency_usd_id)
checked_line += 1
continue
if move_line.account_id.id == self.account_rcv_id:
self.assertEquals(move_line.debit, 38.21)
self.assertEquals(move_line.credit, 0.0)
self.assertEquals(move_line.amount_currency, 50)
self.assertEquals(move_line.currency_id.id, self.currency_swiss_id)
checked_line += 1
continue
if move_line.account_id.id == self.account_rsa_id:
self.assertEquals(move_line.debit, 0.0)
self.assertEquals(move_line.credit, 10.74)
checked_line += 1
continue
self.assertEquals(checked_line, 3)

View File

@ -11,6 +11,7 @@
<link rel="stylesheet" href="/account/static/src/css/account_bank_statement_reconciliation.css"/> <link rel="stylesheet" href="/account/static/src/css/account_bank_statement_reconciliation.css"/>
<script type="text/javascript" src="/account/static/src/js/account_widgets.js"></script> <script type="text/javascript" src="/account/static/src/js/account_widgets.js"></script>
<script type="text/javascript" src="/account/static/src/js/account_move_line_quickadd.js"></script> <script type="text/javascript" src="/account/static/src/js/account_move_line_quickadd.js"></script>
<script type="text/javascript" src="/account/static/src/js/account_tour_bank_statement_reconciliation.js"></script>
</xpath> </xpath>
</template> </template>
</data> </data>

View File

@ -41,47 +41,6 @@ class res_currency(osv.osv):
return res return res
class res_company(osv.osv):
_inherit = "res.company"
_columns = {
'income_currency_exchange_account_id': fields.many2one(
'account.account',
string="Gain Exchange Rate Account",
domain="[('type', '=', 'other')]",),
'expense_currency_exchange_account_id': fields.many2one(
'account.account',
string="Loss Exchange Rate Account",
domain="[('type', '=', 'other')]",),
}
class account_config_settings(osv.osv_memory):
_inherit = 'account.config.settings'
_columns = {
'income_currency_exchange_account_id': fields.related(
'company_id', 'income_currency_exchange_account_id',
type='many2one',
relation='account.account',
string="Gain Exchange Rate Account",
domain="[('type', '=', 'other')]"),
'expense_currency_exchange_account_id': fields.related(
'company_id', 'expense_currency_exchange_account_id',
type="many2one",
relation='account.account',
string="Loss Exchange Rate Account",
domain="[('type', '=', 'other')]"),
}
def onchange_company_id(self, cr, uid, ids, company_id, context=None):
res = super(account_config_settings, self).onchange_company_id(cr, uid, ids, company_id, context=context)
if company_id:
company = self.pool.get('res.company').browse(cr, uid, company_id, context=context)
res['value'].update({'income_currency_exchange_account_id': company.income_currency_exchange_account_id and company.income_currency_exchange_account_id.id or False,
'expense_currency_exchange_account_id': company.expense_currency_exchange_account_id and company.expense_currency_exchange_account_id.id or False})
else:
res['value'].update({'income_currency_exchange_account_id': False,
'expense_currency_exchange_account_id': False})
return res
class account_voucher(osv.osv): class account_voucher(osv.osv):
def _check_paid(self, cr, uid, ids, name, args, context=None): def _check_paid(self, cr, uid, ids, name, args, context=None):
res = {} res = {}

View File

@ -194,26 +194,6 @@
<field name="search_view_id" ref="view_voucher_filter"/> <field name="search_view_id" ref="view_voucher_filter"/>
</record> </record>
<!-- res.config form view -->
<record model="ir.ui.view" id="view_account_settings_currency_xchange_form">
<field name="name">account.config.settings.inherit</field>
<field name="inherit_id" ref="account.view_account_config_settings"/>
<field name="model">account.config.settings</field>
<field name="priority">20</field>
<field name="arch" type="xml">
<xpath expr="//div[@name='group_multi_currency']" position="after">
<group attrs="{'invisible': [('group_multi_currency', '&lt;&gt;', True)]}" col="2">
<group>
<field name="income_currency_exchange_account_id"/>
<field name="expense_currency_exchange_account_id"/>
</group>
<group>
</group>
</group>
</xpath>
</field>
</record>
</data> </data>
<data noupdate="1"> <data noupdate="1">

View File

@ -286,7 +286,7 @@ class account_coda_import(osv.osv_memory):
if 'counterpartyAddress' in line and line['counterpartyAddress'] != '': if 'counterpartyAddress' in line and line['counterpartyAddress'] != '':
note.append(_('Counter Party Address') + ': ' + line['counterpartyAddress']) note.append(_('Counter Party Address') + ': ' + line['counterpartyAddress'])
partner_id = None partner_id = None
structured_com = "" structured_com = False
bank_account_id = False bank_account_id = False
if line['communication_struct'] and 'communication_type' in line and line['communication_type'] == '101': if line['communication_struct'] and 'communication_type' in line and line['communication_type'] == '101':
structured_com = line['communication'] structured_com = line['communication']
@ -322,21 +322,16 @@ class account_coda_import(osv.osv_memory):
self.pool.get('account.bank.statement.line').create(cr, uid, data, context=context) self.pool.get('account.bank.statement.line').create(cr, uid, data, context=context)
if statement['coda_note'] != '': if statement['coda_note'] != '':
self.pool.get('account.bank.statement').write(cr, uid, [statement['id']], {'coda_note': statement['coda_note']}, context=context) self.pool.get('account.bank.statement').write(cr, uid, [statement['id']], {'coda_note': statement['coda_note']}, context=context)
model, action_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'action_bank_statement_tree') model, action_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'action_bank_reconcile_bank_statements')
action = self.pool[model].browse(cr, uid, action_id, context=context) action = self.pool[model].browse(cr, uid, action_id, context=context)
statements_ids = [statement['id'] for statement in statements]
return { return {
'name': action.name, 'name': action.name,
'view_type': action.view_type, 'tag': action.tag,
'view_mode': action.view_mode, 'context': {'statement_ids': statements_ids},
'res_model': action.res_model, 'type': 'ir.actions.client',
'domain': action.domain,
'context': action.context,
'type': 'ir.actions.act_window',
'search_view_id': action.search_view_id.id,
'views': [(v.view_id.id, v.view_mode) for v in action.view_ids]
} }
def rmspaces(s): def rmspaces(s):
return " ".join(s.split()) return " ".join(s.split())

View File

@ -21,6 +21,7 @@
import re import re
import time import time
import math
from openerp import api, fields as fields2 from openerp import api, fields as fields2
from openerp import tools from openerp import tools
@ -270,6 +271,22 @@ class res_currency(osv.osv):
# apply rounding # apply rounding
return to_currency.round(to_amount) if round else to_amount return to_currency.round(to_amount) if round else to_amount
def get_format_currencies_js_function(self, cr, uid, context=None):
""" Returns a string that can be used to instanciate a javascript function that formats numbers as currencies.
That function expects the number as first parameter and the currency id as second parameter. In case of failure it returns undefined."""
function = ""
for row in self.search_read(cr, uid, domain=[], fields=['id', 'name', 'symbol', 'rounding', 'position'], context=context):
digits = int(math.log10(1 / row['rounding']))
symbol = row['symbol'] or row['name']
format_number_str = "openerp.web.format_value(arguments[0], {type: 'float', digits: [69," + str(digits) + "]}, 0.00)"
if row['position'] == 'after':
return_str = "return " + format_number_str + " + '\\xA0" + symbol + "';"
else:
return_str = "return '" + symbol + "\\xA0' + " + format_number_str + ";"
function += "if (arguments[1] === " + str(row['id']) + ") { " + return_str + " }"
return function
class res_currency_rate(osv.osv): class res_currency_rate(osv.osv):
_name = "res.currency.rate" _name = "res.currency.rate"
_description = "Currency Rate" _description = "Currency Rate"
@ -285,4 +302,3 @@ class res_currency_rate(osv.osv):
_order = "name desc" _order = "name desc"
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: