from openerp.tools.translate import _
import openerp.addons.decimal_precision as dp
from openerp.report import report_sxw
+from openerp.tools import float_compare, float_round
+
+import time
class account_bank_statement(osv.osv):
def create(self, cr, uid, vals, context=None):
'ref': st_line.ref,
}
- def _get_counter_part_account(sefl, cr, uid, st_line, context=None):
+ def _get_counter_part_account(self, cr, uid, st_line, context=None):
"""Retrieve the account to use in the counterpart move.
:param browse_record st_line: account.bank.statement.line record to create the move from.
return st_line.statement_id.journal_id.default_credit_account_id.id
return st_line.statement_id.journal_id.default_debit_account_id.id
- def _get_counter_part_partner(sefl, cr, uid, st_line, context=None):
+ def _get_counter_part_partner(self, cr, uid, st_line, context=None):
"""Retrieve the partner to use in the counterpart move.
:param browse_record st_line: account.bank.statement.line record to create the move from.
amt_cur = False
if st_line.statement_id.currency.id != company_currency_id:
amt_cur = st_line.amount
- cur_id = st_line.currency_id or st_line.statement_id.currency.id
- if st_line.currency_id and st_line.amount_currency:
+ cur_id = st_line.statement_id.currency.id
+ elif st_line.currency_id and st_line.amount_currency:
amt_cur = st_line.amount_currency
cur_id = st_line.currency_id.id
return self._prepare_move_line_vals(cr, uid, st_line, move_id, debit, credit,
self.pool.get('account.move').post(cr, uid, move_ids, context=context)
self.message_post(cr, uid, [st.id], body=_('Statement %s confirmed, journal items were created.') % (st.name,), context=context)
self.link_bank_to_partner(cr, uid, ids, context=context)
- return self.write(cr, uid, ids, {'state': 'confirm'}, context=context)
+ return self.write(cr, uid, ids, {'state': 'confirm', 'closing_date': time.strftime("%Y-%m-%d %H:%M:%S")}, context=context)
def button_cancel(self, cr, uid, ids, context=None):
bnk_st_line_ids = []
return {'value': res}
def unlink(self, cr, uid, ids, context=None):
+ statement_line_obj = self.pool['account.bank.statement.line']
for item in self.browse(cr, uid, ids, context=context):
if item.state != 'draft':
raise osv.except_osv(
_('Invalid Action!'),
_('In order to delete a bank statement, you must first cancel it to delete related journal items.')
)
+ # Explicitly unlink bank statement lines
+ # so it will check that the related journal entries have
+ # been deleted first
+ statement_line_obj.unlink(cr, uid, [line.id for line in item.line_ids], context=context)
return super(account_bank_statement, self).unlink(cr, uid, ids, context=context)
def button_journal_entries(self, cr, uid, ids, context=None):
'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')
- return bsl_obj.search_count(cr, uid, [('statement_id', '=', id), ('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
+ return bsl_obj.search_count(cr, uid, [('statement_id', 'in', ids), ('journal_entry_id', '!=', False)], context=context)
def link_bank_to_partner(self, cr, uid, ids, context=None):
for statement in self.browse(cr, uid, ids, context=context):
class account_bank_statement_line(osv.osv):
+ def create(self, cr, uid, vals, context=None):
+ if vals.get('amount_currency', 0) and not vals.get('amount', 0):
+ raise osv.except_osv(_('Error!'), _('If "Amount Currency" is specified, then "Amount" must be as well.'))
+ return super(account_bank_statement_line, self).create(cr, uid, vals, context=context)
+
+ def unlink(self, cr, uid, ids, context=None):
+ for item in self.browse(cr, uid, ids, context=context):
+ if item.journal_entry_id:
+ raise osv.except_osv(
+ _('Invalid Action!'),
+ _('In order to delete a bank statement line, you must first cancel it to delete related journal items.')
+ )
+ return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
+
def cancel(self, cr, uid, ids, context=None):
account_move_obj = self.pool.get('account.move')
move_ids = []
account_move_obj.button_cancel(cr, uid, move_ids, context=context)
account_move_obj.unlink(cr, uid, move_ids, context)
- def get_data_for_reconciliations(self, cr, uid, ids, context=None):
- """ Used to instanciate a batch of reconciliations in a single request """
- # Build a list of reconciliations data
+ def get_data_for_reconciliations(self, cr, uid, ids, excluded_ids=None, search_reconciliation_proposition=True, context=None):
+ """ Returns the data required to display a reconciliation, for each statement line id in ids """
ret = []
- statement_line_done = {}
- mv_line_ids_selected = []
+ if excluded_ids is None:
+ 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:
- if statement_line_done.get(st_line_id):
- ret.append(statement_line_done.get(st_line_id))
+ reconciliation_data = {}
+ 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:
- 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
+ reconciliation_data['reconciliation_proposition'] = []
+ st_line = self.get_statement_line_for_reconciliation(cr, uid, st_line, context=context)
+ reconciliation_data['st_line'] = st_line
+ ret.append(reconciliation_data)
+
return ret
- def get_statement_line_for_reconciliation(self, cr, uid, id, context=None):
- """ Returns the data required by the bank statement reconciliation use case """
- line = self.browse(cr, uid, id, context=context)
- statement_currency = line.journal_id.currency or line.journal_id.company_id.currency_id
- amount = line.amount
- rml_parser = report_sxw.rml_parse(cr, uid, 'statement_line_widget', context=context)
- amount_str = line.amount > 0 and line.amount or -line.amount
- amount_str = rml_parser.formatLang(amount_str, currency_obj=statement_currency)
- amount_currency_str = ""
- if line.amount_currency and line.currency_id:
- amount_currency_str = amount_str
- amount_str = rml_parser.formatLang(line.amount_currency, currency_obj=line.currency_id)
- amount = line.amount_currency
+ def get_statement_line_for_reconciliation(self, cr, uid, st_line, context=None):
+ """ Returns the data required by the bank statement reconciliation widget to display a statement line """
+ if context is None:
+ context = {}
+ statement_currency = st_line.journal_id.currency or st_line.journal_id.company_id.currency_id
+ rml_parser = report_sxw.rml_parse(cr, uid, 'reconciliation_widget_asl', context=context)
+
+ 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_str = amount > 0 and amount or -amount
+ amount_str = rml_parser.formatLang(amount_str, currency_obj=st_line.currency_id or statement_currency)
data = {
- 'id': line.id,
- 'ref': line.ref,
- 'note': line.note or "",
- 'name': line.name,
- 'date': line.date,
+ 'id': st_line.id,
+ 'ref': st_line.ref,
+ 'note': st_line.note or "",
+ 'name': st_line.name,
+ 'date': st_line.date,
'amount': amount,
- 'amount_str': amount_str,
- 'currency_id': 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': line.partner_id.id,
- 'statement_id': line.statement_id.id,
- 'account_code': line.journal_id.default_debit_account_id.code,
- 'account_name': line.journal_id.default_debit_account_id.name,
- 'partner_name': line.partner_id.name,
- 'amount_currency_str': amount_currency_str,
- 'has_no_partner': not line.partner_id.id,
+ 'amount_str': amount_str, # Amount in the statement line currency
+ 'currency_id': st_line.currency_id.id or statement_currency.id,
+ 'partner_id': st_line.partner_id.id,
+ 'statement_id': st_line.statement_id.id,
+ 'account_code': st_line.journal_id.default_debit_account_id.code,
+ 'account_name': st_line.journal_id.default_debit_account_id.name,
+ 'partner_name': st_line.partner_id.name,
+ 'communication_partner_name': st_line.partner_name,
+ 'amount_currency_str': amount_currency_str, # Amount in the statement currency
+ 'has_no_partner': not st_line.partner_id.id,
}
- if line.partner_id.id:
- data['open_balance_account_id'] = line.partner_id.property_account_payable.id
+ if st_line.partner_id.id:
if amount > 0:
- data['open_balance_account_id'] = line.partner_id.property_account_receivable.id
- return data
+ 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
- def search_structured_com(self, cr, uid, st_line, context=None):
- if not st_line.ref:
- return
- domain = [('ref', '=', st_line.ref)]
- 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
+ return data
- def get_reconciliation_proposition(self, cr, uid, id, excluded_ids=[], context=None):
+ def _domain_reconciliation_proposition(self, cr, uid, st_line, excluded_ids=None, context=None):
+ if excluded_ids is None:
+ excluded_ids = []
+ domain = [('ref', '=', st_line.name),
+ ('reconcile_id', '=', False),
+ ('state', '=', 'valid'),
+ ('account_id.reconcile', '=', True),
+ ('id', 'not in', excluded_ids)]
+ return domain
+
+ def get_reconciliation_proposition(self, cr, uid, st_line, excluded_ids=None, context=None):
""" Returns move lines that constitute the best guess to reconcile a statement line. """
- st_line = self.browse(cr, uid, id, context=context)
- company_currency = st_line.journal_id.company_id.currency_id.id
- 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
- if statement_currency == company_currency:
- amount_field = 'credit'
- if st_line.amount > 0:
- amount_field = 'debit'
+ mv_line_pool = self.pool.get('account.move.line')
+
+ # Look for structured communication
+ if st_line.name:
+ domain = self._domain_reconciliation_proposition(cr, uid, st_line, excluded_ids=excluded_ids, context=context)
+ match_id = mv_line_pool.search(cr, uid, 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]
+
+ # How to compare statement line amount and move lines amount
+ precision_digits = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
+ currency_id = st_line.currency_id.id or st_line.journal_id.currency.id
+ # NB : amount can't be == 0 ; so float precision is not an issue for amount > 0 or amount < 0
+ amount = st_line.amount_currency or st_line.amount
+ domain = [('reconcile_partial_id', '=', False)]
+ if currency_id:
+ domain += [('currency_id', '=', currency_id)]
+ sign = 1 # correct the fact that st_line.amount is signed and debit/credit is not
+ amount_field = 'debit'
+ if currency_id == False:
+ if amount < 0:
+ amount_field = 'credit'
+ sign = -1
else:
amount_field = 'amount_currency'
- if st_line.amount < 0:
- sign = -1
- #we don't propose anything if there is no partner detected
+ # Look for a matching amount
+ domain_exact_amount = domain + [(amount_field, '=', float_round(sign * amount, precision_digits=precision_digits))]
+ match_id = self.get_move_lines_for_reconciliation(cr, uid, st_line, excluded_ids=excluded_ids, offset=0, limit=1, additional_domain=domain_exact_amount)
+ if match_id:
+ return match_id
+
if not st_line.partner_id.id:
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
- if sign == -1:
- mv_lines = self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=excluded_ids, additional_domain=[(amount_field, '<', 0)])
+
+ # Look for a set of move line whose amount is <= to the line's amount
+ if amount > 0: # Make sure we can't mix receivable and payable
+ domain += [('account_id.type', '=', 'receivable')]
+ else:
+ domain += [('account_id.type', '=', 'payable')]
+ if amount_field == 'amount_currency' and amount < 0:
+ domain += [(amount_field, '<', 0), (amount_field, '>', (sign * amount))]
else:
- mv_lines = self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids=excluded_ids, additional_domain=[(amount_field, '>', 0)])
+ domain += [(amount_field, '>', 0), (amount_field, '<', (sign * amount))]
+ mv_lines = self.get_move_lines_for_reconciliation(cr, uid, st_line, excluded_ids=excluded_ids, limit=5, additional_domain=domain)
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):
+ total += abs(line['debit'] - line['credit'])
+ if float_compare(total, abs(amount), precision_digits=precision_digits) != 1:
ret.append(line)
- total += line[amount_field]
- if total >= abs(st_line.amount):
+ else:
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)
- return self.get_move_lines_counterparts(cr, uid, st_line, excluded_ids, additional_domain, count, context=context)
-
- def get_move_lines_counterparts(self, cr, uid, st_line, excluded_ids=[], additional_domain=[], count=False, 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
- If count is true, only returns the count.
+ return self.get_move_lines_for_reconciliation(cr, uid, st_line, excluded_ids, str, offset, limit, count, additional_domain, context=context)
+
+ def _domain_move_lines_for_reconciliation(self, cr, uid, st_line, excluded_ids=None, str=False, additional_domain=None, context=None):
+ if excluded_ids is None:
+ excluded_ids = []
+ if additional_domain is None:
+ additional_domain = []
+ # Make domain
+ domain = additional_domain + [
+ ('reconcile_id', '=', False),
+ ('state', '=', 'valid'),
+ ('account_id.reconcile', '=', True)
+ ]
+ if st_line.partner_id.id:
+ domain += [('partner_id', '=', st_line.partner_id.id)]
+ if excluded_ids:
+ domain.append(('id', 'not in', excluded_ids))
+ if str:
+ domain += [
+ '|', ('move_id.name', 'ilike', str),
+ '|', ('move_id.ref', 'ilike', str),
+ ('date_maturity', 'like', str),
+ ]
+ if not st_line.partner_id.id:
+ domain.insert(-1, '|', )
+ domain.append(('partner_id.name', 'ilike', str))
+ if str != '/':
+ domain.insert(-1, '|', )
+ domain.append(('name', 'ilike', str))
+ return domain
+
+ 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. If count is true, only returns the count.
: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 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 tuples list domain: additional domain restrictions
+ :param tuples list additional_domain: additional domain restrictions
"""
mv_line_pool = self.pool.get('account.move.line')
-
- domain = additional_domain + [('reconcile_id', '=', False),('state', '=', 'valid')]
- if st_line.partner_id.id:
- domain += [('partner_id', '=', st_line.partner_id.id),
- '|', ('account_id.type', '=', 'receivable'),
- ('account_id.type', '=', 'payable')]
- else:
- domain += [('account_id.reconcile', '=', True)]
- #domain += [('account_id.reconcile', '=', True), ('account_id.type', '=', 'other')]
- if 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)
- return self.make_counter_part_lines(cr, uid, st_line, line_ids, count=count, context=context)
-
- def make_counter_part_lines(self, cr, uid, st_line, line_ids, count=False, context=None):
- if context is None:
- context = {}
- mv_line_pool = self.pool.get('account.move.line')
- currency_obj = self.pool.get('res.currency')
- 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
+ domain = self._domain_move_lines_for_reconciliation(cr, uid, st_line, excluded_ids=excluded_ids, str=str, additional_domain=additional_domain, context=context)
+
+ # Get move lines ; in case of a partial reconciliation, only consider one line
+ filtered_lines = []
reconcile_partial_ids = []
- if count:
- nb_lines = 0
- for line in mv_line_pool.browse(cr, uid, line_ids, context=context):
+ actual_offset = offset
+ while True:
+ line_ids = mv_line_pool.search(cr, uid, domain, offset=actual_offset, limit=limit, order="date_maturity asc, id asc", context=context)
+ lines = mv_line_pool.browse(cr, uid, line_ids, context=context)
+ make_one_more_loop = False
+ for line in lines:
if line.reconcile_partial_id and line.reconcile_partial_id.id in reconcile_partial_ids:
+ #if we filtered a line because it is partially reconciled with an already selected line, we must do one more loop
+ #in order to get the right number of items in the pager
+ make_one_more_loop = True
continue
- nb_lines += 1
+ filtered_lines.append(line)
if line.reconcile_partial_id:
reconcile_partial_ids.append(line.reconcile_partial_id.id)
- return nb_lines
+
+ if not limit or not make_one_more_loop or len(filtered_lines) >= limit:
+ break
+ actual_offset = actual_offset + limit
+ lines = limit and filtered_lines[:limit] or filtered_lines
+
+ # Either return number of lines
+ if count:
+ return len(lines)
+
+ # Or return list of dicts representing the formatted move lines
else:
- ret = []
- for line in mv_line_pool.browse(cr, uid, line_ids, context=context):
- if line.reconcile_partial_id and line.reconcile_partial_id.id in reconcile_partial_ids:
- continue
- 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)
- 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
- if st_line.currency_id and line.currency_id and line.currency_id.id == st_line.currency_id.id:
- if line.amount_residual_currency < 0:
- ret_line['debit'] = 0
- ret_line['credit'] = -line.amount_residual_currency
- else:
- ret_line['debit'] = line.amount_residual_currency if line.credit != 0 else 0
- ret_line['credit'] = line.amount_residual_currency if line.debit != 0 else 0
- ret_line['amount_currency_str'] = rml_parser.formatLang(line.amount_residual, currency_obj=company_currency)
- else:
- if line.amount_residual < 0:
- ret_line['debit'] = 0
- ret_line['credit'] = -line.amount_residual
- else:
- ret_line['debit'] = line.amount_residual if line.credit != 0 else 0
- ret_line['credit'] = line.amount_residual if line.debit != 0 else 0
- 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
+ target_currency = st_line.currency_id or st_line.journal_id.currency or st_line.journal_id.company_id.currency_id
+ 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)
+ has_no_partner = not bool(st_line.partner_id.id)
+ for line in mv_lines:
+ line['has_no_partner'] = has_no_partner
+ return mv_lines
def get_currency_rate_line(self, cr, uid, st_line, currency_diff, move_id, context=None):
if currency_diff < 0:
'statement_id': st_line.statement_id.id,
'debit': currency_diff < 0 and -currency_diff or 0,
'credit': currency_diff > 0 and currency_diff or 0,
+ 'amount_currency': 0.0,
'date': st_line.date,
'account_id': account_id
}
+ def process_reconciliations(self, cr, uid, data, context=None):
+ for datum in data:
+ self.process_reconciliation(cr, uid, datum[0], datum[1], context=context)
+
def process_reconciliation(self, cr, uid, id, mv_line_dicts, context=None):
""" Creates a move line for each item of mv_line_dicts and for the statement line. Reconcile a new move line with its counterpart_move_line_id if specified. Finally, mark the statement line as reconciled by putting the newly created move id in the column journal_entry_id.
raise osv.except_osv(_('Error!'), _('A selected move line was already reconciled.'))
# Create the move
- move_name = st_line.statement_id.name + "/" + str(st_line.sequence)
+ move_name = (st_line.statement_id.name or st_line.name) + "/" + str(st_line.sequence)
move_vals = bs_obj._prepare_move(cr, uid, st_line, move_name, context=context)
move_id = am_obj.create(cr, uid, move_vals, context=context)
# Create the move line for the statement line
- ctx = context.copy()
- ctx['date'] = st_line.date
- amount = currency_obj.compute(cr, uid, st_line.statement_id.currency.id, company_currency.id, st_line.amount, context=ctx)
+ if st_line.statement_id.currency.id != company_currency.id:
+ if st_line.currency_id == company_currency:
+ amount = st_line.amount_currency
+ else:
+ ctx = context.copy()
+ ctx['date'] = st_line.date
+ amount = currency_obj.compute(cr, uid, st_line.statement_id.currency.id, company_currency.id, st_line.amount, context=ctx)
+ else:
+ amount = st_line.amount
bank_st_move_vals = bs_obj._prepare_bank_move_line(cr, uid, st_line, move_id, amount, company_currency.id, context=context)
aml_obj.create(cr, uid, bank_st_move_vals, context=context)
# Complete the dicts
st_line_currency = st_line.currency_id or statement_currency
- st_line_currency_rate = st_line.currency_id and statement_currency.id == company_currency.id and (st_line.amount_currency / st_line.amount) or False
+ st_line_currency_rate = st_line.currency_id and (st_line.amount_currency / st_line.amount) or False
to_create = []
for mv_line_dict in mv_line_dicts:
if mv_line_dict.get('is_tax_line'):
mv_line_dict['statement_id'] = st_line.statement_id.id
if mv_line_dict.get('counterpart_move_line_id'):
mv_line = aml_obj.browse(cr, uid, mv_line_dict['counterpart_move_line_id'], context=context)
+ mv_line_dict['partner_id'] = mv_line.partner_id.id or st_line.partner_id.id
mv_line_dict['account_id'] = mv_line.account_id.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['currency_id'] = st_line_currency.id
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)
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:
+ 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=ctx)
else:
- debit_at_current_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['debit'], context=context)
- credit_at_current_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['credit'], 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=ctx)
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
- ctx = context.copy()
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)
credit_at_old_rate = currency_obj.compute(cr, uid, st_line_currency.id, company_currency.id, mv_line_dict['credit'], context=ctx)
mv_line_dict['debit'] = debit_at_old_rate
if debit_at_old_rate - debit_at_current_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:
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))
else:
mv_line_dict['debit'] = debit_at_current_rate
mv_line_dict['credit'] = credit_at_current_rate
+ elif statement_currency.id != company_currency.id:
+ #statement is in foreign currency but the transaction is in company currency
+ prorata_factor = (mv_line_dict['debit'] - mv_line_dict['credit']) / st_line.amount_currency
+ mv_line_dict['amount_currency'] = prorata_factor * st_line.amount
to_create.append(mv_line_dict)
# Create move lines
move_line_pairs_to_reconcile = []
new_aml_id = aml_obj.create(cr, uid, mv_line_dict, context=context)
if counterpart_move_line_id != None:
move_line_pairs_to_reconcile.append([new_aml_id, counterpart_move_line_id])
-
# Reconcile
for pair in move_line_pairs_to_reconcile:
- # TODO : too slow
aml_obj.reconcile_partial(cr, uid, pair, context=context)
-
# Mark the statement line as reconciled
self.write(cr, uid, id, {'journal_entry_id': move_id}, context=context)
# Unfortunately, that spawns a "no access rights" error ; it shouldn't.
def _needaction_domain_get(self, cr, uid, context=None):
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"
_name = "account.bank.statement.line"
_description = "Bank Statement Line"
_inherit = ['ir.needaction_mixin']
_columns = {
- 'name': fields.char('Description', required=True),
+ 'name': fields.char('Communication', required=True),
'date': fields.date('Date', required=True),
'amount': fields.float('Amount', digits_compute=dp.get_precision('Account')),
'partner_id': fields.many2one('res.partner', 'Partner'),
'bank_account_id': fields.many2one('res.partner.bank','Bank Account'),
'account_id': fields.many2one('account.account', 'Account', help="This technical field can be used at the statement line creation/import time in order to avoid the reconciliation process on it later on. The statement line will simply create a counterpart on this account"),
- 'statement_id': fields.many2one('account.bank.statement', 'Statement', select=True, required=True, ondelete='cascade'),
+ 'statement_id': fields.many2one('account.bank.statement', 'Statement', select=True, required=True, ondelete='restrict'),
'journal_id': fields.related('statement_id', 'journal_id', type='many2one', relation='account.journal', string='Journal', store=True, readonly=True),
- 'ref': fields.char('Structured Communication'),
+ 'partner_name': fields.char('Partner Name', help="This field is used to record the third party name when importing bank statement in electronic format, when the partner doesn't exist yet in the database (or cannot be found)."),
+ 'ref': fields.char('Reference'),
'note': fields.text('Notes'),
'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of bank statement lines."),
'company_id': fields.related('statement_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
'currency_id': fields.many2one('res.currency', 'Currency', help="The optional other currency if it is a multi-currency entry."),
}
_defaults = {
- 'name': lambda self,cr,uid,context={}: self.pool.get('ir.sequence').get(cr, uid, 'account.bank.statement.line'),
'date': lambda self,cr,uid,context={}: context.get('date', fields.date.context_today(self,cr,uid,context=context)),
}
_description = "Preset for the lines that can be created in a bank statement reconciliation"
_columns = {
'name': fields.char('Button Label', required=True),
- 'account_id': fields.many2one('account.account', 'Account', ondelete='cascade', domain=[('type','!=','view')]),
- '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', required=True),
- 'amount': fields.float('Amount', digits_compute=dp.get_precision('Account'), help="Leave to 0 to ignore."),
- 'tax_id': fields.many2one('account.tax', 'Tax', ondelete='cascade'),
- 'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete='cascade'),
+ 'account_id': fields.many2one('account.account', 'Account', ondelete='cascade', domain=[('type', 'not in', ('view', 'closed', 'consolidation'))]),
+ 'label': fields.char('Journal Item Label'),
+ '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': fields.float('Amount', digits_compute=dp.get_precision('Account'), required=True, 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')."),
+ 'tax_id': fields.many2one('account.tax', 'Tax', ondelete='restrict', domain=[('type_tax_use','in',('purchase','all')), ('parent_id','=',False)]),
+ 'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', ondelete='set null', domain=[('type','!=','view'), ('state','not in',('close','cancelled'))]),
+ 'company_id': fields.many2one('res.company', 'Company', required=True),
}
_defaults = {
- 'amount_type': 'fixed',
- 'amount': 0.0
+ 'amount_type': 'percentage_of_balance',
+ 'amount': 100.0,
+ 'company_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: