Browse Source

[ENH] Support multiple accounts/currencies.

Copy of changes proposed for master.
pull/15/head
Ronald Portier (Therp BV) 10 years ago
parent
commit
9945643158
  1. 286
      account_bank_statement_import/account_bank_statement_import.py
  2. 8
      account_bank_statement_import_ofx/account_bank_statement_import_ofx.py
  3. 4
      account_bank_statement_import_qif/account_bank_statement_import_qif.py

286
account_bank_statement_import/account_bank_statement_import.py

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
"""Framework for importing bank statement files."""
import base64
from openerp import api, models, fields
@ -50,7 +51,7 @@ class AccountBankStatementImport(models.TransientModel):
@api.multi
def import_file(self):
""" Process the file chosen in the wizard, create bank statement(s) and
go to reconciliation. """
go to reconciliation."""
self.ensure_one()
data_file = base64.b64decode(self.data_file)
statement_ids, notifications = self.with_context(
@ -70,15 +71,35 @@ class AccountBankStatementImport(models.TransientModel):
@api.model
def _import_file(self, data_file):
""" Create bank statement(s) from file
"""
""" Create bank statement(s) from file."""
# The appropriate implementation module returns the required data
currency_code, account_number, stmts_vals = self._parse_file(data_file)
# Check raw data
self._check_parsed_data(stmts_vals)
statement_ids = []
notifications = []
statements = self._parse_file(data_file)
# Check raw data:
self._check_parsed_data(statements)
# Import all statements:
for statement in statements:
(statement_id, new_notifications) = (
self._import_statement(statement))
if statement_id:
statement_ids.append(statement_id)
notifications.append(new_notifications)
if len(statement_ids) == 0:
raise Warning(_('You have already imported that file.'))
return statement_ids, notifications
@api.model
def _import_statement(self, statement):
"""Import a single bank-statement.
Return ids of created statements and notifications.
"""
currency_code = statement.pop('currency_code')
account_number = statement.pop('account_number')
# Try to find the bank account and currency in odoo
currency_id, bank_account_id = self._find_additional_data(
currency_code, account_number)
currency_id = self._find_currency_id(currency_code)
bank_account_id = self._find_bank_account_id(account_number)
# Create the bank account if not already existing
if not bank_account_id and account_number:
journal_id = self.env.context.get('journal_id')
@ -90,13 +111,15 @@ class AccountBankStatementImport(models.TransientModel):
account_number, company_id=company_id,
currency_id=currency_id).id
# Find or create the bank journal
journal_id = self._get_journal(
currency_id, bank_account_id, account_number)
journal_id = self._get_journal(currency_id, bank_account_id)
# By now journal and account_number must be known
if not journal_id:
raise Warning(_('Can not determine journal for import.'))
# Prepare statement data to be used for bank statements creation
stmts_vals = self._complete_stmts_vals(
stmts_vals, journal_id, account_number)
# Create the bank statements
return self._create_bank_statements(stmts_vals)
statement = self._complete_statement(
statement, journal_id, account_number)
# Create the bank statement
return self._create_bank_statement(statement)
@api.model
def _parse_file(self, data_file):
@ -105,69 +128,70 @@ class AccountBankStatementImport(models.TransientModel):
chain of responsability.
This method parses the given file and returns the data required by
the bank statement import process, as specified below.
rtype: triplet (if a value can't be retrieved, use None)
- currency code: string (e.g: 'EUR')
The ISO 4217 currency code, case insensitive
- account number: string (e.g: 'BE1234567890')
The number of the bank account which the statement belongs
to
- bank statements data: list of dict containing (optional
items marked by o) :
- 'name': string (e.g: '000000123')
- 'date': date (e.g: 2013-06-26)
-o 'balance_start': float (e.g: 8368.56)
-o 'balance_end_real': float (e.g: 8888.88)
- 'transactions': list of dict containing :
- 'name': string
(e.g: 'KBC-INVESTERINGSKREDIET 787-5562831-01')
- 'date': date
- 'amount': float
- 'unique_import_id': string
-o 'account_number': string
Will be used to find/create the res.partner.bank
in odoo
-o 'note': string
-o 'partner_name': string
-o 'ref': string
- bank statements data: list of dict containing (optional
items marked by o) :
-o currency code: string (e.g: 'EUR')
The ISO 4217 currency code, case insensitive
-o account number: string (e.g: 'BE1234567890')
The number of the bank account which the statement
belongs to
- 'name': string (e.g: '000000123')
- 'date': date (e.g: 2013-06-26)
-o 'balance_start': float (e.g: 8368.56)
-o 'balance_end_real': float (e.g: 8888.88)
- 'transactions': list of dict containing :
- 'name': string
(e.g: 'KBC-INVESTERINGSKREDIET 787-5562831-01')
- 'date': date
- 'amount': float
- 'unique_import_id': string
-o 'account_number': string
Will be used to find/create the res.partner.bank
in odoo
-o 'note': string
-o 'partner_name': string
-o 'ref': string
"""
raise Warning(_('Could not make sense of the given file.\nDid you '
'install the module to support this type of file ?'))
raise Warning(_(
'Could not make sense of the given file.\nDid you '
'install the module to support this type of file ?'
))
@api.model
def _check_parsed_data(self, stmts_vals):
def _check_parsed_data(self, statements):
""" Basic and structural verifications """
if len(stmts_vals) == 0:
if len(statements) == 0:
raise Warning(_('This file doesn\'t contain any statement.'))
no_st_line = True
for vals in stmts_vals:
if vals['transactions'] and len(vals['transactions']) > 0:
no_st_line = False
break
if no_st_line:
raise Warning(_('This file doesn\'t contain any transaction.'))
for statement in statements:
if 'transactions' in statement and statement['transactions']:
return
# If we get here, no transaction was found:
raise Warning(_('This file doesn\'t contain any transaction.'))
@api.model
def _find_additional_data(self, currency_code, account_number):
""" Get the res.currency ID and the res.partner.bank ID """
# if no currency_code is provided, we'll use the company currency
currency_id = False
def _find_currency_id(self, currency_code):
""" Get res.currency ID."""
if currency_code:
currency_ids = self.env['res.currency'].search(
[('name', '=ilike', currency_code)])
company_currency_id = self.env.user.company_id.currency_id
if currency_ids:
if currency_ids[0] != company_currency_id:
currency_id = currency_ids[0].id
return currency_ids[0].id
else:
raise Warning(_(
'Statement has invalid currency code %s') % currency_code)
# if no currency_code is provided, we'll use the company currency
return self.env.user.company_id.currency_id.id
@api.model
def _find_bank_account_id(self, account_number):
""" Get res.partner.bank ID """
bank_account_id = None
if account_number and len(account_number) > 4:
bank_account_ids = self.env['res.partner.bank'].search(
[('acc_number', '=', account_number)], limit=1)
if bank_account_ids:
bank_account_id = bank_account_ids[0].id
return currency_id, bank_account_id
return bank_account_id
@api.model
def _get_journal(self, currency_id, bank_account_id, account_number):
@ -189,17 +213,23 @@ class AccountBankStatementImport(models.TransientModel):
if bank_account.journal_id.id:
journal_id = bank_account.journal_id.id
# If importing into an existing journal, its currency must be the same
# as the bank statement
if journal_id:
# as the bank statement. When journal has no currency, currency must
# be equal to company currency.
if journal_id and currency_id:
journal_currency_id = self.env['account.journal'].browse(
journal_id).currency.id
if currency_id and currency_id != journal_currency_id:
raise Warning(_('The currency of the bank statement is not '
'the same as the currency of the journal !'))
# If we couldn't find/create a journal, everything is lost
if not journal_id:
raise Warning(_('Cannot find in which journal import this '
'statement. Please manually select a journal.'))
if journal_currency_id:
if currency_id and currency_id != journal_currency_id:
raise Warning(_(
'The currency of the bank statement is not '
'the same as the currency of the journal !'
))
else:
if currency_id != self.env.user.company_id.currency_id.id:
raise Warning(_(
'The currency of the bank statement is not '
'the same as the company currency !'
))
return journal_id
@api.model
@ -233,77 +263,68 @@ class AccountBankStatementImport(models.TransientModel):
default_currency=currency_id).create(vals_acc)
@api.model
def _complete_stmts_vals(self, stmts_vals, journal_id, account_number):
for st_vals in stmts_vals:
st_vals['journal_id'] = journal_id
def _complete_statement(self, statement, journal_id, account_number):
statement['journal_id'] = journal_id
for line_vals in statement['transactions']:
unique_import_id = line_vals.get('unique_import_id', False)
if unique_import_id:
line_vals['unique_import_id'] = (
account_number and account_number + '-' or '') + \
unique_import_id
for line_vals in st_vals['transactions']:
unique_import_id = line_vals.get('unique_import_id', False)
if unique_import_id:
line_vals['unique_import_id'] = (
account_number and account_number + '-' or '') + \
unique_import_id
if not line_vals.get('bank_account_id'):
# Find the partner and his bank account or create the bank
# account. The partner selected during the reconciliation
# process will be linked to the bank when the statement is
# closed.
partner_id = False
bank_account_id = False
identifying_string = line_vals.get('account_number')
if identifying_string:
bank_model = self.env['res.partner.bank']
banks = bank_model.search(
[('acc_number', '=', identifying_string)], limit=1)
if banks:
bank_account_id = banks[0].id
partner_id = banks[0].partner_id.id
else:
bank_account_id = self._create_bank_account(
identifying_string).id
line_vals['partner_id'] = partner_id
line_vals['bank_account_id'] = bank_account_id
return stmts_vals
if not line_vals.get('bank_account_id'):
# Find the partner and his bank account or create the bank
# account. The partner selected during the reconciliation
# process will be linked to the bank when the statement is
# closed.
partner_id = False
bank_account_id = False
identifying_string = line_vals.get('account_number')
if identifying_string:
bank_model = self.env['res.partner.bank']
banks = bank_model.search(
[('acc_number', '=', identifying_string)], limit=1)
if banks:
bank_account_id = banks[0].id
partner_id = banks[0].partner_id.id
else:
bank_account_id = self._create_bank_account(
identifying_string).id
line_vals['partner_id'] = partner_id
line_vals['bank_account_id'] = bank_account_id
return statement
@api.model
def _create_bank_statements(self, stmts_vals):
""" Create new bank statements from imported values, filtering out
already imported transactions, and returns data used by the
def _create_bank_statement(self, statement):
""" Create bank statement from imported values, filtering out
already imported transactions, and return data used by the
reconciliation widget
"""
bs_model = self.env['account.bank.statement']
bsl_model = self.env['account.bank.statement.line']
# Filter out already imported transactions and create statements
statement_ids = []
# Filter out already imported transactions and create statement
ignored_statement_lines_import_ids = []
for st_vals in stmts_vals:
filtered_st_lines = []
for line_vals in st_vals['transactions']:
if 'unique_import_id' not in line_vals \
or not line_vals['unique_import_id'] \
or not bool(bsl_model.sudo().search(
[('unique_import_id', '=',
line_vals['unique_import_id'])],
limit=1)):
filtered_st_lines.append(line_vals)
else:
ignored_statement_lines_import_ids.append(
line_vals['unique_import_id'])
if len(filtered_st_lines) > 0:
# Remove values that won't be used to create records
st_vals.pop('transactions', None)
for line_vals in filtered_st_lines:
line_vals.pop('account_number', None)
# Create the satement
st_vals['line_ids'] = [[0, False, line] for line in
filtered_st_lines]
statement_ids.append(bs_model.create(st_vals).id)
if len(statement_ids) == 0:
raise Warning(_('You have already imported that file.'))
filtered_st_lines = []
for line_vals in statement['transactions']:
unique_id = (
'unique_import_id' in line_vals and
line_vals['unique_import_id']
)
if not unique_id or not bool(bsl_model.sudo().search(
[('unique_import_id', '=', unique_id)], limit=1)):
filtered_st_lines.append(line_vals)
else:
ignored_statement_lines_import_ids.append(unique_id)
statement_id = False
if len(filtered_st_lines) > 0:
# Remove values that won't be used to create records
statement.pop('transactions', None)
for line_vals in filtered_st_lines:
line_vals.pop('account_number', None)
# Create the statement
statement['line_ids'] = [
[0, False, line] for line in filtered_st_lines]
statement_id = bs_model.create(statement).id
# Prepare import feedback
notifications = []
num_ignored = len(ignored_statement_lines_import_ids)
@ -323,5 +344,4 @@ class AccountBankStatementImport(models.TransientModel):
ignored_statement_lines_import_ids)]).ids
}
}]
return statement_ids, notifications
return statement_id, notifications

8
account_bank_statement_import_ofx/account_bank_statement_import_ofx.py

@ -67,12 +67,12 @@ class AccountBankStatementImport(models.TransientModel):
raise Warning(_("The following problem occurred during import. "
"The file might not be valid.\n\n %s" % e.message))
vals_bank_statement = {
return [{
'currency_code': ofx.account.statement.currency,
'account_number': ofx.account.number,
'name': ofx.account.routing_number,
'transactions': transactions,
'balance_start': ofx.account.statement.balance,
'balance_end_real':
float(ofx.account.statement.balance) + total_amt,
}
return ofx.account.statement.currency, ofx.account.number, [
vals_bank_statement]
}]

4
account_bank_statement_import_qif/account_bank_statement_import_qif.py

@ -84,7 +84,9 @@ class AccountBankStatementImport(models.TransientModel):
'not correctly formed.'))
vals_bank_statement.update({
'currency_code': None,
'account_number': None,
'balance_end_real': total,
'transactions': transactions
})
return None, None, [vals_bank_statement]
return [vals_bank_statement]
Loading…
Cancel
Save