[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 for st_line in self.browse(cr, uid, ids, context=context):
exact_match_id = self.search_structured_com(cr, uid, st_line, context=context) reconciliation_data = {}
if exact_match_id: if search_reconciliation_proposition:
reconciliation_data = { reconciliation_proposition = self.get_reconciliation_proposition(cr, uid, st_line, excluded_ids=excluded_ids, context=context)
'st_line': self.get_statement_line_for_reconciliation(cr, uid, st_line.id, context), for mv_line in reconciliation_proposition:
'reconciliation_proposition': self.make_counter_part_lines(cr, uid, st_line, [exact_match_id], context=context) excluded_ids.append(mv_line['id'])
} reconciliation_data['reconciliation_proposition'] = reconciliation_proposition
for mv_line in reconciliation_data['reconciliation_proposition']: else:
mv_line_ids_selected.append(mv_line['id']) reconciliation_data['reconciliation_proposition'] = []
statement_line_done[st_line.id] = reconciliation_data st_line = self.get_statement_line_for_reconciliation(cr, uid, st_line, context=context)
reconciliation_data['st_line'] = st_line
for st_line_id in ids: ret.append(reconciliation_data)
if statement_line_done.get(st_line_id):
ret.append(statement_line_done.get(st_line_id))
else:
reconciliation_data = {
'st_line': self.get_statement_line_for_reconciliation(cr, uid, st_line_id, context),
'reconciliation_proposition': self.get_reconciliation_proposition(cr, uid, st_line_id, mv_line_ids_selected, context)
}
for mv_line in reconciliation_data['reconciliation_proposition']:
mv_line_ids_selected.append(mv_line['id'])
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_currency_str = "" amount = st_line.amount_currency
if line.amount_currency and line.currency_id: amount_currency = st_line.amount
amount_currency_str = amount_str amount_currency_str = amount_currency > 0 and amount_currency or -amount_currency
amount_str = rml_parser.formatLang(line.amount_currency, currency_obj=line.currency_id) amount_currency_str = rml_parser.formatLang(amount_currency_str, currency_obj=statement_currency)
amount = line.amount_currency else:
amount = st_line.amount
amount_currency_str = ""
amount_str = amount > 0 and amount or -amount
amount_str = rml_parser.formatLang(amount_str, currency_obj=st_line.currency_id or statement_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 [] return [match_id[0]]
# 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 return []
if sign == -1:
mv_lines = self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=excluded_ids, additional_domain=[(amount_field, '<', 0)])
else:
mv_lines = self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=excluded_ids, additional_domain=[(amount_field, '>', 0)])
ret = []
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): 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):
""" Bridge between the web client reconciliation widget and get_move_lines_for_reconciliation (which expects a browse record) """
if excluded_ids is None:
excluded_ids = []
if additional_domain is None:
additional_domain = []
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

@ -44,7 +44,7 @@
<field name="partner_id" ref="base.res_partner_17"/> <field name="partner_id" ref="base.res_partner_17"/>
<field name="name">Zed+ Antivirus</field> <field name="name">Zed+ Antivirus</field>
</record> </record>
<!-- Some customer invoices used to show the reconciliation process on the bank statement --> <!-- Some customer invoices used to show the reconciliation process on the bank statement -->
<record id="invoice_1" model="account.invoice"> <record id="invoice_1" model="account.invoice">
<field name="currency_id" ref="base.EUR"/> <field name="currency_id" ref="base.EUR"/>

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; }
} }
@ -434,4 +456,4 @@ $initialLineBackground: #f0f0f0;
} }
} }
} }
} }

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")'
},
]
});
}());

File diff suppressed because it is too large Load Diff

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

@ -193,26 +193,6 @@
<field name="context">{'state':'posted'}</field> <field name="context">{'state':'posted'}</field>
<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: