[FIX] account: bank statement import
- use a wizard to ask about creating a new bank account + journal - fix 'Import Statement' action from the dashboard - make l10n_be_coda new-api-compliant
This commit is contained in:
committed by
Arthur Maniet
parent
cc5a2e44a8
commit
2d9569cc18
@@ -343,5 +343,7 @@ class account_journal(models.Model):
|
||||
action_name = 'action_account_bank_statement_import'
|
||||
ir_model_obj = self.pool['ir.model.data']
|
||||
model, action_id = ir_model_obj.get_object_reference(self._cr, self._uid, 'account_bank_statement_import', action_name)
|
||||
action = self.pool[model].read(self._cr, self._uid, action_id, context=self._context)
|
||||
action = self.pool[model].read(self._cr, self._uid, action_id, context=self.env.context)
|
||||
# Note: this drops action['context'], which is a dict stored as a string, which is not easy to update
|
||||
action.update({'context': (u"{'journal_id': " + str(self.id) + u"}")})
|
||||
return action
|
||||
|
||||
@@ -30,25 +30,24 @@ class AccountBankStatementImport(models.TransientModel):
|
||||
def import_file(self):
|
||||
""" Process the file chosen in the wizard, create bank statement(s) and go to reconciliation. """
|
||||
self.ensure_one()
|
||||
rec = self.with_context(active_id=self.ids[0])
|
||||
#set the active_id in the context, so that any extension module could
|
||||
#reuse the fields chosen in the wizard if needed (see .QIF for example)
|
||||
data_file = self.data_file
|
||||
# The appropriate implementation module returns the required data
|
||||
currency_code, account_number, stmts_vals = rec._parse_file(base64.b64decode(data_file))
|
||||
# Let the appropriate implementation module parse the file and return the required data
|
||||
# The active_id is passed in context in case an implementation module requires information about the wizard state (see QIF)
|
||||
currency_code, account_number, stmts_vals = self.with_context(active_id=self.ids[0])._parse_file(base64.b64decode(data_file))
|
||||
# Check raw data
|
||||
rec._check_parsed_data(stmts_vals)
|
||||
# Try to find the bank account and currency in odoo
|
||||
currency_id, bank_account_id = rec._find_additional_data(currency_code, account_number)
|
||||
# Find or create the bank journal
|
||||
journal_id = rec._get_journal(currency_id, bank_account_id, account_number)
|
||||
# Create the bank account if not already existing
|
||||
if not bank_account_id and account_number:
|
||||
rec._create_bank_account(account_number, journal_id=journal_id)
|
||||
self._check_parsed_data(stmts_vals)
|
||||
# Try to find the currency and journal in odoo
|
||||
currency, journal, bank_account = self._find_additional_data(currency_code, account_number)
|
||||
# If no journal found, ask the user about creating one
|
||||
if not journal:
|
||||
# The active_id is passed in context so the wizard can call import_file again once the journal is created
|
||||
return self.with_context(active_id=self.ids[0])._journal_creation_wizard(currency, account_number, bank_account)
|
||||
# Prepare statement data to be used for bank statements creation
|
||||
stmts_vals = rec._complete_stmts_vals(stmts_vals, journal_id, account_number)
|
||||
stmts_vals = self._complete_stmts_vals(stmts_vals, journal, account_number)
|
||||
# Create the bank statements
|
||||
statement_ids, notifications = rec._create_bank_statements(stmts_vals)
|
||||
statement_ids, notifications = self._create_bank_statements(stmts_vals)
|
||||
# Finally dispatch to reconciliation interface
|
||||
action = self.env.ref('account.action_bank_reconcile_bank_statements')
|
||||
return {
|
||||
@@ -61,6 +60,24 @@ class AccountBankStatementImport(models.TransientModel):
|
||||
'type': 'ir.actions.client',
|
||||
}
|
||||
|
||||
def _journal_creation_wizard(self, currency, account_number, bank_account):
|
||||
""" Calls a wizard that allows the user to accept/refuse journal creation """
|
||||
return {
|
||||
'name': _('Journal Creation'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'account.bank.statement.import.journal.creation',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'context': {
|
||||
'statement_import_transient_id': self.env.context['active_id'],
|
||||
'default_currency_id': currency and currency.id or False,
|
||||
'default_account_number': account_number,
|
||||
'bank_account_id': bank_account and bank_account.id or False,
|
||||
'default_name': _('Bank') + ' ' + account_number,
|
||||
}
|
||||
}
|
||||
|
||||
def _parse_file(self, data_file):
|
||||
""" Each module adding a file support must extends this method. It processes the file if it can, returns super otherwise, resulting in a chain of responsability.
|
||||
This method parses the given file and returns the data required by the bank statement import process, as specified below.
|
||||
@@ -102,83 +119,71 @@ class AccountBankStatementImport(models.TransientModel):
|
||||
|
||||
def _find_additional_data(self, currency_code, account_number):
|
||||
""" Get the res.currency ID and the res.partner.bank ID """
|
||||
currency_id = False # So if no currency_code is provided, we'll use the company currency
|
||||
company_currency = self.env.user.company_id.currency_id
|
||||
journal_obj = self.env['account.journal']
|
||||
currency = None
|
||||
|
||||
if currency_code:
|
||||
currency = self.env['res.currency'].search([('name', '=ilike', currency_code)], limit=1)
|
||||
company_currency = self.env.user.company_id.currency_id
|
||||
if currency.id != company_currency.id:
|
||||
currency_id = currency.id
|
||||
if not currency:
|
||||
raise osv.except_osv(_("No currency found matching '%s'.") % currency_code)
|
||||
if currency == company_currency:
|
||||
currency = False
|
||||
|
||||
bank_account_id = None
|
||||
bank_account = None
|
||||
if account_number and len(account_number) > 4:
|
||||
account_number = account_number.replace(' ', '').replace('-', '')
|
||||
self.env.cr.execute("select id from res_partner_bank where replace(replace(acc_number,' ',''),'-','') = %s", (account_number,))
|
||||
bank_account_ids = [id[0] for id in self.env.cr.fetchall()]
|
||||
bank_account_ids = self.env['res.partner.bank'].search([('id', 'in', bank_account_ids)], limit=1)
|
||||
if bank_account_ids:
|
||||
bank_account_id = bank_account_ids.id
|
||||
|
||||
return currency_id, bank_account_id
|
||||
|
||||
def _get_journal(self, currency_id, bank_account_id, account_number):
|
||||
""" Find or create the journal """
|
||||
ResPartnerBank = self.env['res.partner.bank']
|
||||
bank_account = self.env['res.partner.bank'].search([('acc_number', '=', account_number)], limit=1)
|
||||
|
||||
# Find the journal from context or bank account
|
||||
journal_id = self._context.get('journal_id')
|
||||
if bank_account_id:
|
||||
bank_account = ResPartnerBank.browse(bank_account_id)
|
||||
if journal_id:
|
||||
if bank_account.journal_id.id and bank_account.journal_id.id != journal_id:
|
||||
journal = journal_obj.browse(self._context.get('journal_id', []))
|
||||
if bank_account:
|
||||
if journal:
|
||||
if bank_account.journal_id and bank_account.journal_id != journal:
|
||||
raise UserError(_('The account of this statement is linked to another journal.'))
|
||||
if not bank_account.journal_id.id:
|
||||
bank_account.write({'journal_id': journal_id})
|
||||
if not bank_account.journal_id:
|
||||
bank_account.write({'journal_id': journal.id})
|
||||
else:
|
||||
if bank_account.journal_id.id:
|
||||
journal_id = bank_account.journal_id.id
|
||||
if bank_account.journal_id:
|
||||
journal = bank_account.journal_id
|
||||
|
||||
# If importing into an existing journal, its currency must be the same as the bank statement
|
||||
if journal_id:
|
||||
journal_currency_id = self.env['account.journal'].browse(journal_id).currency_id.id
|
||||
if currency_id and currency_id != journal_currency_id:
|
||||
raise UserError(_('The currency of the bank statement is not the same as the currency of the journal !'))
|
||||
# If importing into an existing journal
|
||||
if journal:
|
||||
# The bank account cannot belong to another journal
|
||||
if not bank_account and account_number:
|
||||
journal_account = self.env['res.partner.bank'].search([('journal_id', '=', journal.id)], limit=1)
|
||||
if journal_account:
|
||||
raise UserError(_('You are importing a file from account %s while the account of journal %s is %s.') % (account_number, journal.name, journal_account.acc_number))
|
||||
|
||||
# If there is no journal, create one (and its account)
|
||||
if not journal_id and account_number:
|
||||
company = self.env.user.company_id
|
||||
journal_vals = self.env['account.journal']._prepare_bank_journal(company, {'account_type': 'bank', 'acc_name': account_number, 'currency_id': currency_id})
|
||||
journal_id = self.env['account.journal'].create(journal_vals).id
|
||||
if bank_account_id:
|
||||
bank_account.write({'journal_id': journal_id})
|
||||
# Its currency must be the same as the bank statement
|
||||
journal_currency = journal.currency_id
|
||||
if currency == None:
|
||||
currency = journal_currency
|
||||
if currency and currency != journal_currency:
|
||||
statement_cur_code = currency == False and company_currency.name or currency.name
|
||||
journal_cur_code = not journal_currency and company_currency.name or journal_currency.name
|
||||
raise UserError(_('The currency of the bank statement (%s) is not the same as the currency of the journal (%s) !') % (statement_cur_code, journal_cur_code))
|
||||
|
||||
# If we couldn't find/create a journal, everything is lost
|
||||
if not journal_id:
|
||||
# If we couldn't find / can't create a journal, everything is lost
|
||||
if not journal and not account_number:
|
||||
raise UserError(_('Cannot find in which journal import this statement. Please manually select a journal.'))
|
||||
return journal_id
|
||||
|
||||
def _create_bank_account(self, account_number, journal_id=False):
|
||||
return currency, journal, bank_account
|
||||
|
||||
def _create_bank_account(self, account_number):
|
||||
try:
|
||||
bank_type = self.env.ref('bank.bank_normal')
|
||||
bank_code = bank_type.code
|
||||
except ValueError:
|
||||
bank_code = 'bank'
|
||||
account_number = account_number.replace(' ', '').replace('-', '')
|
||||
vals_acc = {
|
||||
return self.env['res.partner.bank'].create({
|
||||
'acc_number': account_number,
|
||||
'state': bank_code,
|
||||
}
|
||||
# Odoo users bank accounts (which we import statement from) have company_id and journal_id set
|
||||
# while 'counterpart' bank accounts (from which statement transactions originate) don't.
|
||||
if journal_id:
|
||||
vals_acc['journal_id'] = journal_id
|
||||
vals_acc['company_id'] = self.env.user.company_id.id
|
||||
vals_acc['partner_id'] = self.env.user.company_id.partner_id.id
|
||||
})
|
||||
|
||||
return self.env['res.partner.bank'].create(vals_acc)
|
||||
|
||||
def _complete_stmts_vals(self, stmts_vals, journal_id, account_number):
|
||||
def _complete_stmts_vals(self, stmts_vals, journal, account_number):
|
||||
for st_vals in stmts_vals:
|
||||
st_vals['journal_id'] = journal_id
|
||||
st_vals['journal_id'] = journal.id
|
||||
|
||||
for line_vals in st_vals['transactions']:
|
||||
unique_import_id = line_vals.get('unique_import_id')
|
||||
|
||||
@@ -19,7 +19,7 @@ class account_bank_statement_import_journal_creation(models.TransientModel):
|
||||
currency_id = wiz.currency_id.id
|
||||
account_number = wiz.account_number
|
||||
|
||||
bank_account_id = self.env._context.get('bank_account_id')
|
||||
bank_account_id = self.env.context.get('bank_account_id')
|
||||
if bank_account_id:
|
||||
vals = {'currency_id': currency_id, 'acc_name': account_number, 'account_type': 'bank'}
|
||||
vals_journal = journal_obj._prepare_bank_journal(company, vals)
|
||||
@@ -38,5 +38,5 @@ class account_bank_statement_import_journal_creation(models.TransientModel):
|
||||
self.env['res.partner.bank'].create(res_partner_bank_vals)
|
||||
|
||||
# Finish the statement import
|
||||
statement_import_transient = import_wiz_obj.browse(self.env._context['statement_import_transient_id'])
|
||||
statement_import_transient = import_wiz_obj.browse(self.env.context['statement_import_transient_id'])
|
||||
return statement_import_transient.import_file()
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<p>Just click OK to create the account/journal and finish the import. If this was a mistake, hit cancel to abort the import.</p>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="currency_id" options="{'no_create': True}"/>
|
||||
<field name="currency_id" options="{'no_create': True}" attrs="{'invisible': [('currency_id', '=', False)]}"/>
|
||||
<field name="account_number"/>
|
||||
</group>
|
||||
<footer>
|
||||
|
||||
@@ -11,19 +11,18 @@ class AccountBankStatementImport(models.TransientModel):
|
||||
_inherit = "account.bank.statement.import"
|
||||
|
||||
def _get_hide_journal_field(self):
|
||||
return self._context and 'journal_id' in self._context or False
|
||||
return self.env.context and 'journal_id' in self.env.context or False
|
||||
|
||||
journal_id = fields.Many2one('account.journal', string='Journal', help='Accounting journal related to the bank statement you\'re importing. It has be be manually chosen for statement formats which doesn\'t allow automatic journal detection (QIF for example).')
|
||||
hide_journal_field = fields.Boolean(string='Hide the journal field in the view', default=_get_hide_journal_field)
|
||||
|
||||
def _get_journal(self, currency_id, bank_account_id, account_number):
|
||||
def _find_additional_data(self, *args):
|
||||
""" As .QIF format does not allow us to detect the journal, we need to let the user choose it.
|
||||
We set it in context before to call super so it's the same as calling the widget from a journal """
|
||||
if self._context.get('active_id'):
|
||||
record = self.browse(self._context.get('active_id'))
|
||||
if record.journal_id:
|
||||
return super(AccountBankStatementImport, self).with_context(journal_id=record.journal_id.id)._get_journal(currency_id, bank_account_id, account_number)
|
||||
return super(AccountBankStatementImport, self)._get_journal(currency_id, bank_account_id, account_number)
|
||||
We set it in context in the same way it's done when calling the import action from a journal.
|
||||
"""
|
||||
if self.journal_id:
|
||||
self.env.context = dict(self.env.context, journal_id=self.journal_id.id)
|
||||
return super(AccountBankStatementImport, self)._find_additional_data(*args)
|
||||
|
||||
def _check_qif(self, data_file):
|
||||
return data_file.strip().startswith('!Type:')
|
||||
|
||||
@@ -10,11 +10,9 @@
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='data_file']" position="after">
|
||||
<field name="hide_journal_field" invisible="1"/>
|
||||
<label for="journal_id"/>
|
||||
<field name="journal_id"
|
||||
domain="[('type', '=', 'bank')]"
|
||||
attrs="{'invisible': [('hide_journal_field', '=', True)]}"
|
||||
context="{'default_type':'bank'}"/>
|
||||
<group attrs="{'invisible': [('hide_journal_field', '=', True)]}">
|
||||
<field name="journal_id" domain="[('type', '=', 'bank')]" context="{'default_type':'bank'}"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -57,7 +57,6 @@ Wizards provided by this module:
|
||||
'account',
|
||||
'base_vat',
|
||||
'base_iban',
|
||||
'l10n_be_coda',
|
||||
'l10n_multilang',
|
||||
],
|
||||
'data': [
|
||||
|
||||
@@ -71,14 +71,14 @@ description provided by the CODA configuration tables is based upon the CODA
|
||||
V2.2 specifications.
|
||||
If required, you can manually adjust the descriptions via the CODA configuration menu.
|
||||
''',
|
||||
'depends': ['account_voucher', 'base_iban', 'l10n_be_invoice_bba', 'account_bank_statement_import'],
|
||||
'depends': ['account_accountant', 'l10n_be'],
|
||||
'demo': [
|
||||
'l10n_be_coda_demo.xml',
|
||||
],
|
||||
'data': [
|
||||
'l10n_be_coda_view.xml',
|
||||
],
|
||||
'auto_install': False,
|
||||
'auto_install': True,
|
||||
'website': 'https://www.odoo.com/page/accounting',
|
||||
'installable': True,
|
||||
'license': 'AGPL-3',
|
||||
|
||||
@@ -22,28 +22,23 @@
|
||||
import time
|
||||
import re
|
||||
|
||||
from openerp.osv import osv
|
||||
from openerp.tools.translate import _
|
||||
from openerp import tools
|
||||
from openerp import models, tools, _
|
||||
from openerp.exceptions import UserError
|
||||
|
||||
import logging
|
||||
class AccountBankStatementImport(models.TransientModel):
|
||||
_inherit = 'account.bank.statement.import'
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class account_bank_statement_import(osv.TransientModel):
|
||||
_inherit = "account.bank.statement.import"
|
||||
|
||||
def _check_coda(self, cr, uid, data_file, context=None):
|
||||
def _check_coda(self, data_file):
|
||||
# Matches the first 24 characters of a CODA file, as defined by the febelfin specifications
|
||||
return re.match('0{5}\d{9}05[ D] +', data_file) != None
|
||||
|
||||
def _parse_file(self, cr, uid, data_file, context=None):
|
||||
if not self._check_coda(cr, uid, data_file, context=context):
|
||||
return super(account_bank_statement_import, self)._parse_file(cr, uid, data_file, context=context)
|
||||
def _parse_file(self, data_file):
|
||||
if not self._check_coda(data_file):
|
||||
return super(AccountBankStatementImport, self)._parse_file(data_file)
|
||||
|
||||
def rmspaces(s):
|
||||
return " ".join(s.split())
|
||||
|
||||
if context is None:
|
||||
context = {}
|
||||
recordlist = unicode(data_file, 'windows-1252', 'strict').split('\n')
|
||||
statements = []
|
||||
globalisation_comm = {}
|
||||
@@ -251,5 +246,3 @@ class account_bank_statement_import(osv.TransientModel):
|
||||
currency_code = statement['currency']
|
||||
acc_number = statements[0] and statements[0]['acc_number'] or False
|
||||
return currency_code, acc_number, ret_statements
|
||||
def rmspaces(s):
|
||||
return " ".join(s.split())
|
||||
|
||||
Reference in New Issue
Block a user