mirror of
https://github.com/OCA/account-reconcile.git
synced 2025-01-20 12:27:39 +02:00
[IMP] account_statement_base_import: Fix PEP8
This commit is contained in:
@@ -31,30 +31,34 @@
|
|||||||
],
|
],
|
||||||
'description': """
|
'description': """
|
||||||
This module brings basic methods and fields on bank statement to deal with
|
This module brings basic methods and fields on bank statement to deal with
|
||||||
the importation of different bank and offices. A generic abstract method is defined and an
|
the importation of different bank and offices. A generic abstract method is
|
||||||
example that gives you a basic way of importing bank statement through a standard file is provided.
|
defined and an example that gives you a basic way of importing bank statement
|
||||||
|
through a standard file is provided.
|
||||||
|
|
||||||
This module improves the bank statement and allows you to import your bank transactions with
|
This module improves the bank statement and allows you to import your bank
|
||||||
a standard .csv or .xls file (you'll find it in the 'data' folder). It respects the profile
|
transactions with a standard .csv or .xls file (you'll find it in the 'data'
|
||||||
(provided by the accouhnt_statement_ext module) to pass the entries. That means,
|
folder). It respects the profile (provided by the accouhnt_statement_ext
|
||||||
you'll have to choose a file format for each profile.
|
module) to pass the entries. That means, you'll have to choose a file format
|
||||||
In order to achieve this it uses the `xlrd` Python module which you will need to install
|
for each profile.
|
||||||
separately in your environment.
|
In order to achieve this it uses the `xlrd` Python module which you will need
|
||||||
|
to install separately in your environment.
|
||||||
|
|
||||||
This module can handle a commission taken by the payment office and has the following format:
|
This module can handle a commission taken by the payment office and has the
|
||||||
|
following format:
|
||||||
|
|
||||||
* ref : the SO number, INV number or any matching ref found. It'll be used as reference
|
* __ref__: the SO number, INV number or any matching ref found. It'll be used
|
||||||
in the generated entries and will be useful for reconciliation process
|
as reference in the generated entries and will be useful for reconciliation
|
||||||
* date : date of the payment
|
process
|
||||||
* amount : amount paid in the currency of the journal used in the importation profile
|
* __date__: date of the payment
|
||||||
* label : the comunication given by the payment office, used as communication in the
|
* __amount__: amount paid in the currency of the journal used in the
|
||||||
generated entries.
|
importation profile
|
||||||
|
* __label__: the comunication given by the payment office, used as
|
||||||
The goal is here to populate the statement lines of a bank statement with the infos that the
|
communication in the generated entries.
|
||||||
bank or office give you. Fell free to inherit from this module to add your own format.Then,
|
|
||||||
if you need to complete data from there, add your own account_statement_*_completion module and implement
|
|
||||||
the needed rules.
|
|
||||||
|
|
||||||
|
The goal is here to populate the statement lines of a bank statement with the
|
||||||
|
infos that the bank or office give you. Fell free to inherit from this module
|
||||||
|
to add your own format. Then, if you need to complete data from there, add your
|
||||||
|
own account_statement_*_completion module and implement the needed rules.
|
||||||
""",
|
""",
|
||||||
'website': 'http://www.camptocamp.com',
|
'website': 'http://www.camptocamp.com',
|
||||||
'data': [
|
'data': [
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
from openerp.tools.translate import _
|
from openerp.tools.translate import _
|
||||||
from openerp.osv.osv import except_osv
|
from openerp.osv.orm import except_orm
|
||||||
import tempfile
|
import tempfile
|
||||||
import datetime
|
import datetime
|
||||||
from parser import BankStatementImportParser
|
from parser import BankStatementImportParser
|
||||||
@@ -36,26 +36,28 @@ def float_or_zero(val):
|
|||||||
|
|
||||||
|
|
||||||
class FileParser(BankStatementImportParser):
|
class FileParser(BankStatementImportParser):
|
||||||
|
"""Generic abstract class for defining parser for .csv, .xls or .xlsx file
|
||||||
"""
|
format.
|
||||||
Generic abstract class for defining parser for .csv, .xls or .xlsx file format.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, parse_name, ftype='csv', extra_fields=None, header=None, **kwargs):
|
def __init__(self, parse_name, ftype='csv', extra_fields=None, header=None,
|
||||||
|
**kwargs):
|
||||||
"""
|
"""
|
||||||
:param char: parse_name: The name of the parser
|
:param char: parse_name: The name of the parser
|
||||||
:param char: ftype: extension of the file (could be csv, xls or xlsx)
|
:param char: ftype: extension of the file (could be csv, xls or \
|
||||||
:param dict: extra_fields: extra fields to add to the conversion dict. In the format
|
xlsx)
|
||||||
{fieldname: fieldtype}
|
:param dict: extra_fields: extra fields to add to the conversion \
|
||||||
:param list: header : specify header fields if the csv file has no header
|
dict. In the format {fieldname: fieldtype}
|
||||||
|
:param list: header : specify header fields if the csv file has no \
|
||||||
|
header
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super(FileParser, self).__init__(parse_name, **kwargs)
|
super(FileParser, self).__init__(parse_name, **kwargs)
|
||||||
if ftype in ('csv', 'xls', 'xlsx'):
|
if ftype in ('csv', 'xls', 'xlsx'):
|
||||||
self.ftype = ftype[0:3]
|
self.ftype = ftype[0:3]
|
||||||
else:
|
else:
|
||||||
raise except_osv(_('User Error'),
|
raise except_orm(
|
||||||
_('Invalid file type %s. Please use csv, xls or xlsx') % ftype)
|
_('User Error'),
|
||||||
|
_('Invalid file type %s. Please use csv, xls or xlsx') % ftype)
|
||||||
self.conversion_dict = {
|
self.conversion_dict = {
|
||||||
'ref': unicode,
|
'ref': unicode,
|
||||||
'label': unicode,
|
'label': unicode,
|
||||||
@@ -71,23 +73,17 @@ class FileParser(BankStatementImportParser):
|
|||||||
# Set in _parse_xls, from the contents of the file
|
# Set in _parse_xls, from the contents of the file
|
||||||
|
|
||||||
def _custom_format(self, *args, **kwargs):
|
def _custom_format(self, *args, **kwargs):
|
||||||
"""
|
"""No other work on data are needed in this parser."""
|
||||||
No other work on data are needed in this parser.
|
|
||||||
"""
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _pre(self, *args, **kwargs):
|
def _pre(self, *args, **kwargs):
|
||||||
"""
|
"""No pre-treatment needed for this parser."""
|
||||||
No pre-treatment needed for this parser.
|
|
||||||
"""
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _parse(self, *args, **kwargs):
|
def _parse(self, *args, **kwargs):
|
||||||
"""
|
"""Launch the parsing through .csv, .xls or .xlsx depending on the
|
||||||
Launch the parsing through .csv, .xls or .xlsx depending on the
|
|
||||||
given ftype
|
given ftype
|
||||||
"""
|
"""
|
||||||
|
|
||||||
res = None
|
res = None
|
||||||
if self.ftype == 'csv':
|
if self.ftype == 'csv':
|
||||||
res = self._parse_csv()
|
res = self._parse_csv()
|
||||||
@@ -97,31 +93,27 @@ class FileParser(BankStatementImportParser):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _validate(self, *args, **kwargs):
|
def _validate(self, *args, **kwargs):
|
||||||
"""
|
"""We check that all the key of the given file (means header) are
|
||||||
We check that all the key of the given file (means header) are present
|
present in the validation key provided. Otherwise, we raise an
|
||||||
in the validation key provided. Otherwise, we raise an Exception.
|
Exception. We skip the validation step if the file header is provided
|
||||||
We skip the validation step if the file header is provided separately
|
separately (in the field: fieldnames).
|
||||||
(in the field: fieldnames).
|
|
||||||
"""
|
"""
|
||||||
if self.fieldnames is None:
|
if self.fieldnames is None:
|
||||||
parsed_cols = self.result_row_list[0].keys()
|
parsed_cols = self.result_row_list[0].keys()
|
||||||
for col in self.keys_to_validate:
|
for col in self.keys_to_validate:
|
||||||
if col not in parsed_cols:
|
if col not in parsed_cols:
|
||||||
raise except_osv(_('Invalid data'),
|
raise except_orm(_('Invalid data'),
|
||||||
_('Column %s not present in file') % col)
|
_('Column %s not present in file') % col)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _post(self, *args, **kwargs):
|
def _post(self, *args, **kwargs):
|
||||||
"""
|
"""Cast row type depending on the file format .csv or .xls after
|
||||||
Cast row type depending on the file format .csv or .xls after parsing the file.
|
parsing the file."""
|
||||||
"""
|
|
||||||
self.result_row_list = self._cast_rows(*args, **kwargs)
|
self.result_row_list = self._cast_rows(*args, **kwargs)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _parse_csv(self):
|
def _parse_csv(self):
|
||||||
"""
|
""":return: list of dict from csv file (line/rows)"""
|
||||||
:return: list of dict from csv file (line/rows)
|
|
||||||
"""
|
|
||||||
csv_file = tempfile.NamedTemporaryFile()
|
csv_file = tempfile.NamedTemporaryFile()
|
||||||
csv_file.write(self.filebuffer)
|
csv_file.write(self.filebuffer)
|
||||||
csv_file.flush()
|
csv_file.flush()
|
||||||
@@ -130,9 +122,7 @@ class FileParser(BankStatementImportParser):
|
|||||||
return list(reader)
|
return list(reader)
|
||||||
|
|
||||||
def _parse_xls(self):
|
def _parse_xls(self):
|
||||||
"""
|
""":return: dict of dict from xls/xlsx file (line/rows)"""
|
||||||
:return: dict of dict from xls/xlsx file (line/rows)
|
|
||||||
"""
|
|
||||||
wb_file = tempfile.NamedTemporaryFile()
|
wb_file = tempfile.NamedTemporaryFile()
|
||||||
wb_file.write(self.filebuffer)
|
wb_file.write(self.filebuffer)
|
||||||
# We ensure that cursor is at beginig of file
|
# We ensure that cursor is at beginig of file
|
||||||
@@ -147,8 +137,7 @@ class FileParser(BankStatementImportParser):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
def _from_csv(self, result_set, conversion_rules):
|
def _from_csv(self, result_set, conversion_rules):
|
||||||
"""
|
"""Handle the converstion from the dict and handle date format from
|
||||||
Handle the converstion from the dict and handle date format from
|
|
||||||
an .csv file.
|
an .csv file.
|
||||||
"""
|
"""
|
||||||
for line in result_set:
|
for line in result_set:
|
||||||
@@ -159,72 +148,60 @@ class FileParser(BankStatementImportParser):
|
|||||||
line[rule] = datetime.datetime.strptime(date_string,
|
line[rule] = datetime.datetime.strptime(date_string,
|
||||||
'%Y-%m-%d')
|
'%Y-%m-%d')
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
raise except_osv(_("Date format is not valid."),
|
raise except_orm(
|
||||||
_(" It should be YYYY-MM-DD for column: %s"
|
_("Date format is not valid."),
|
||||||
" value: %s \n \n"
|
_(" It should be YYYY-MM-DD for column: %s"
|
||||||
" \n Please check the line with ref: %s"
|
" value: %s \n \n \n Please check the line with "
|
||||||
" \n \n Detail: %s") % (rule,
|
"ref: %s \n \n Detail: %s") %
|
||||||
line.get(
|
(rule, line.get(rule, _('Missing')),
|
||||||
rule, _('Missing')),
|
line.get('ref', line), repr(err)))
|
||||||
line.get(
|
|
||||||
'ref', line),
|
|
||||||
repr(err)))
|
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
line[rule] = conversion_rules[rule](line[rule])
|
line[rule] = conversion_rules[rule](line[rule])
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
raise except_osv(_('Invalid data'),
|
raise except_orm(
|
||||||
_("Value %s of column %s is not valid."
|
_('Invalid data'),
|
||||||
"\n Please check the line with ref %s:"
|
_("Value %s of column %s is not valid.\n Please "
|
||||||
"\n \n Detail: %s") % (line.get(rule, _('Missing')),
|
"check the line with ref %s:\n \n Detail: %s") %
|
||||||
rule,
|
(line.get(rule, _('Missing')), rule,
|
||||||
line.get(
|
line.get('ref', line), repr(err)))
|
||||||
'ref', line),
|
|
||||||
repr(err)))
|
|
||||||
return result_set
|
return result_set
|
||||||
|
|
||||||
def _from_xls(self, result_set, conversion_rules):
|
def _from_xls(self, result_set, conversion_rules):
|
||||||
"""
|
"""Handle the converstion from the dict and handle date format from
|
||||||
Handle the converstion from the dict and handle date format from
|
|
||||||
an .csv, .xls or .xlsx file.
|
an .csv, .xls or .xlsx file.
|
||||||
"""
|
"""
|
||||||
for line in result_set:
|
for line in result_set:
|
||||||
for rule in conversion_rules:
|
for rule in conversion_rules:
|
||||||
if conversion_rules[rule] == datetime.datetime:
|
if conversion_rules[rule] == datetime.datetime:
|
||||||
try:
|
try:
|
||||||
t_tuple = xlrd.xldate_as_tuple(
|
t_tuple = xlrd.xldate_as_tuple(line[rule],
|
||||||
line[rule], self._datemode)
|
self._datemode)
|
||||||
line[rule] = datetime.datetime(*t_tuple)
|
line[rule] = datetime.datetime(*t_tuple)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
raise except_osv(_("Date format is not valid"),
|
raise except_orm(
|
||||||
_("Please modify the cell formatting to date format"
|
_("Date format is not valid"),
|
||||||
" for column: %s"
|
_("Please modify the cell formatting to date format"
|
||||||
" value: %s"
|
" for column: %s value: %s\n Please check the "
|
||||||
"\n Please check the line with ref: %s"
|
"line with ref: %s\n \n Detail: %s") %
|
||||||
"\n \n Detail: %s") % (rule,
|
(rule, line.get(rule, _('Missing')),
|
||||||
line.get(
|
line.get('ref', line), repr(err)))
|
||||||
rule, _('Missing')),
|
|
||||||
line.get(
|
|
||||||
'ref', line),
|
|
||||||
repr(err)))
|
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
line[rule] = conversion_rules[rule](line[rule])
|
line[rule] = conversion_rules[rule](line[rule])
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
raise except_osv(_('Invalid data'),
|
raise except_orm(
|
||||||
_("Value %s of column %s is not valid."
|
_('Invalid data'),
|
||||||
"\n Please check the line with ref %s:"
|
_("Value %s of column %s is not valid.\n Please "
|
||||||
"\n \n Detail: %s") % (line.get(rule, _('Missing')),
|
"check the line with ref %s:\n \n Detail: %s") %
|
||||||
rule,
|
(line.get(rule, _('Missing')), rule,
|
||||||
line.get(
|
line.get('ref', line), repr(err)))
|
||||||
'ref', line),
|
|
||||||
repr(err)))
|
|
||||||
return result_set
|
return result_set
|
||||||
|
|
||||||
def _cast_rows(self, *args, **kwargs):
|
def _cast_rows(self, *args, **kwargs):
|
||||||
"""
|
"""Convert the self.result_row_list using the self.conversion_dict
|
||||||
Convert the self.result_row_list using the self.conversion_dict providen.
|
providen. We call here _from_xls or _from_csv depending on the
|
||||||
We call here _from_xls or _from_csv depending on the self.ftype variable.
|
self.ftype variable.
|
||||||
"""
|
"""
|
||||||
func = getattr(self, '_from_%s' % self.ftype)
|
func = getattr(self, '_from_%s' % self.ftype)
|
||||||
res = func(self.result_row_list, self.conversion_dict)
|
res = func(self.result_row_list, self.conversion_dict)
|
||||||
|
|||||||
@@ -31,9 +31,7 @@ except:
|
|||||||
|
|
||||||
|
|
||||||
class GenericFileParser(FileParser):
|
class GenericFileParser(FileParser):
|
||||||
|
"""Standard parser that use a define format in csv or xls to import into a
|
||||||
"""
|
|
||||||
Standard parser that use a define format in csv or xls to import into a
|
|
||||||
bank statement. This is mostely an example of how to proceed to create a new
|
bank statement. This is mostely an example of how to proceed to create a new
|
||||||
parser, but will also be useful as it allow to import a basic flat file.
|
parser, but will also be useful as it allow to import a basic flat file.
|
||||||
"""
|
"""
|
||||||
@@ -44,8 +42,7 @@ class GenericFileParser(FileParser):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parser_for(cls, parser_name):
|
def parser_for(cls, parser_name):
|
||||||
"""
|
"""Used by the new_bank_statement_parser class factory. Return true if
|
||||||
Used by the new_bank_statement_parser class factory. Return true if
|
|
||||||
the providen name is generic_csvxls_so
|
the providen name is generic_csvxls_so
|
||||||
"""
|
"""
|
||||||
return parser_name == 'generic_csvxls_so'
|
return parser_name == 'generic_csvxls_so'
|
||||||
@@ -56,9 +53,10 @@ class GenericFileParser(FileParser):
|
|||||||
method of statement line in order to record it. It is the responsibility
|
method of statement line in order to record it. It is the responsibility
|
||||||
of every parser to give this dict of vals, so each one can implement his
|
of every parser to give this dict of vals, so each one can implement his
|
||||||
own way of recording the lines.
|
own way of recording the lines.
|
||||||
:param: line: a dict of vals that represent a line of result_row_list
|
:param: line: a dict of vals that represent a line of \
|
||||||
:return: dict of values to give to the create method of statement line,
|
result_row_list
|
||||||
it MUST contain at least:
|
:return: dict of values to give to the create method of statement \
|
||||||
|
line, it MUST contain at least:
|
||||||
{
|
{
|
||||||
'name':value,
|
'name':value,
|
||||||
'date':value,
|
'date':value,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
import base64
|
import base64
|
||||||
import csv
|
import csv
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from openerp.tools.translate import _
|
||||||
|
|
||||||
|
|
||||||
def UnicodeDictReader(utf8_data, **kwargs):
|
def UnicodeDictReader(utf8_data, **kwargs):
|
||||||
@@ -31,7 +32,8 @@ def UnicodeDictReader(utf8_data, **kwargs):
|
|||||||
dialect = sniffer.sniff(sample_data, delimiters=',;\t')
|
dialect = sniffer.sniff(sample_data, delimiters=',;\t')
|
||||||
csv_reader = csv.DictReader(utf8_data, dialect=dialect, **kwargs)
|
csv_reader = csv.DictReader(utf8_data, dialect=dialect, **kwargs)
|
||||||
for row in csv_reader:
|
for row in csv_reader:
|
||||||
yield dict([(key, unicode(value, 'utf-8')) for key, value in row.iteritems()])
|
yield dict([(key, unicode(value, 'utf-8')) for key, value in
|
||||||
|
row.iteritems()])
|
||||||
|
|
||||||
|
|
||||||
class BankStatementImportParser(object):
|
class BankStatementImportParser(object):
|
||||||
@@ -61,23 +63,19 @@ class BankStatementImportParser(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parser_for(cls, parser_name):
|
def parser_for(cls, parser_name):
|
||||||
"""
|
"""Override this method for every new parser, so that
|
||||||
Override this method for every new parser, so that new_bank_statement_parser can
|
new_bank_statement_parser can return the good class from his name.
|
||||||
return the good class from his name.
|
|
||||||
"""
|
"""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _decode_64b_stream(self):
|
def _decode_64b_stream(self):
|
||||||
"""
|
"""Decode self.filebuffer in base 64 and override it"""
|
||||||
Decode self.filebuffer in base 64 and override it
|
|
||||||
"""
|
|
||||||
self.filebuffer = base64.b64decode(self.filebuffer)
|
self.filebuffer = base64.b64decode(self.filebuffer)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _format(self, decode_base_64=True, **kwargs):
|
def _format(self, decode_base_64=True, **kwargs):
|
||||||
"""
|
"""Decode into base 64 if asked and Format the given filebuffer by
|
||||||
Decode into base 64 if asked and Format the given filebuffer by calling
|
calling _custom_format method.
|
||||||
_custom_format method.
|
|
||||||
"""
|
"""
|
||||||
if decode_base_64:
|
if decode_base_64:
|
||||||
self._decode_64b_stream()
|
self._decode_64b_stream()
|
||||||
@@ -85,44 +83,40 @@ class BankStatementImportParser(object):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _custom_format(self, *args, **kwargs):
|
def _custom_format(self, *args, **kwargs):
|
||||||
"""
|
"""Implement a method in your parser to convert format, encoding and so
|
||||||
Implement a method in your parser to convert format, encoding and so on before
|
on before starting to work on datas. Work on self.filebuffer
|
||||||
starting to work on datas. Work on self.filebuffer
|
|
||||||
"""
|
"""
|
||||||
return NotImplementedError
|
return NotImplementedError
|
||||||
|
|
||||||
def _pre(self, *args, **kwargs):
|
def _pre(self, *args, **kwargs):
|
||||||
"""
|
"""Implement a method in your parser to make a pre-treatment on datas
|
||||||
Implement a method in your parser to make a pre-treatment on datas before parsing
|
before parsing them, like concatenate stuff, and so... Work on
|
||||||
them, like concatenate stuff, and so... Work on self.filebuffer
|
self.filebuffer
|
||||||
"""
|
"""
|
||||||
return NotImplementedError
|
return NotImplementedError
|
||||||
|
|
||||||
def _parse(self, *args, **kwargs):
|
def _parse(self, *args, **kwargs):
|
||||||
"""
|
"""Implement a method in your parser to save the result of parsing
|
||||||
Implement a method in your parser to save the result of parsing self.filebuffer
|
self.filebuffer in self.result_row_list instance property.
|
||||||
in self.result_row_list instance property.
|
|
||||||
"""
|
"""
|
||||||
return NotImplementedError
|
return NotImplementedError
|
||||||
|
|
||||||
def _validate(self, *args, **kwargs):
|
def _validate(self, *args, **kwargs):
|
||||||
"""
|
"""Implement a method in your parser to validate the
|
||||||
Implement a method in your parser to validate the self.result_row_list instance
|
self.result_row_list instance property and raise an error if not valid.
|
||||||
property and raise an error if not valid.
|
|
||||||
"""
|
"""
|
||||||
return NotImplementedError
|
return NotImplementedError
|
||||||
|
|
||||||
def _post(self, *args, **kwargs):
|
def _post(self, *args, **kwargs):
|
||||||
"""
|
"""Implement a method in your parser to make some last changes on the
|
||||||
Implement a method in your parser to make some last changes on the result of parsing
|
result of parsing the datas, like converting dates, computing
|
||||||
the datas, like converting dates, computing commission, ...
|
commission, ...
|
||||||
"""
|
"""
|
||||||
return NotImplementedError
|
return NotImplementedError
|
||||||
|
|
||||||
def get_st_vals(self):
|
def get_st_vals(self):
|
||||||
"""
|
"""This method return a dict of vals that ca be passed to create method
|
||||||
This method return a dict of vals that ca be passed to
|
of statement.
|
||||||
create method of statement.
|
|
||||||
:return: dict of vals that represent additional infos for the statement
|
:return: dict of vals that represent additional infos for the statement
|
||||||
"""
|
"""
|
||||||
return {
|
return {
|
||||||
@@ -133,33 +127,32 @@ class BankStatementImportParser(object):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def get_st_line_vals(self, line, *args, **kwargs):
|
def get_st_line_vals(self, line, *args, **kwargs):
|
||||||
"""
|
"""Implement a method in your parser that must return a dict of vals
|
||||||
Implement a method in your parser that must return a dict of vals that can be
|
that can be passed to create method of statement line in order to record
|
||||||
passed to create method of statement line in order to record it. It is the responsibility
|
it. It is the responsibility of every parser to give this dict of vals,
|
||||||
of every parser to give this dict of vals, so each one can implement his
|
so each one can implement his own way of recording the lines.
|
||||||
own way of recording the lines.
|
|
||||||
:param: line: a dict of vals that represent a line of result_row_list
|
:param: line: a dict of vals that represent a line of result_row_list
|
||||||
:return: dict of values to give to the create method of statement line,
|
:return: dict of values to give to the create method of statement line,\
|
||||||
it MUST contain at least:
|
it MUST contain at least:
|
||||||
{
|
{
|
||||||
'name':value,
|
'name':value,
|
||||||
'date':value,
|
'date':value,
|
||||||
'amount':value,
|
'amount':value,
|
||||||
'ref':value,
|
'ref':value,
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
return NotImplementedError
|
return NotImplementedError
|
||||||
|
|
||||||
def parse(self, filebuffer, *args, **kwargs):
|
def parse(self, filebuffer, *args, **kwargs):
|
||||||
"""
|
"""This will be the method that will be called by wizard, button and so
|
||||||
This will be the method that will be called by wizard, button and so
|
|
||||||
to parse a filebuffer by calling successively all the private method
|
to parse a filebuffer by calling successively all the private method
|
||||||
that need to be define for each parser.
|
that need to be define for each parser.
|
||||||
Return:
|
Return:
|
||||||
[] of rows as {'key':value}
|
[] of rows as {'key':value}
|
||||||
|
|
||||||
Note: The row_list must contain only value that are present in the account.
|
Note: The row_list must contain only value that are present in the
|
||||||
bank.statement.line object !!!
|
account.bank.statement.line object !!!
|
||||||
"""
|
"""
|
||||||
if filebuffer:
|
if filebuffer:
|
||||||
self.filebuffer = filebuffer
|
self.filebuffer = filebuffer
|
||||||
|
|||||||
@@ -20,16 +20,14 @@
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from openerp.tools.translate import _
|
from openerp.tools.translate import _
|
||||||
import datetime
|
import datetime
|
||||||
from openerp.osv.orm import Model
|
from openerp.osv import fields, orm
|
||||||
from openerp.osv import fields, osv
|
|
||||||
from parser import new_bank_statement_parser
|
from parser import new_bank_statement_parser
|
||||||
from openerp.tools.config import config
|
from openerp.tools.config import config
|
||||||
|
|
||||||
|
|
||||||
class AccountStatementProfil(Model):
|
class AccountStatementProfil(orm.Model):
|
||||||
_inherit = "account.statement.profile"
|
_inherit = "account.statement.profile"
|
||||||
|
|
||||||
def _get_import_type_selection(self, cr, uid, context=None):
|
def _get_import_type_selection(self, cr, uid, context=None):
|
||||||
@@ -62,52 +60,51 @@ class AccountStatementProfil(Model):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _write_extra_statement_lines(
|
def _write_extra_statement_lines(
|
||||||
self, cr, uid, parser, result_row_list, profile, statement_id, context):
|
self, cr, uid, parser, result_row_list, profile, statement_id,
|
||||||
|
context):
|
||||||
"""Insert extra lines after the main statement lines.
|
"""Insert extra lines after the main statement lines.
|
||||||
|
|
||||||
After the main statement lines have been created, you can override this method to create
|
After the main statement lines have been created, you can override this
|
||||||
extra statement lines.
|
method to create extra statement lines.
|
||||||
|
|
||||||
:param: browse_record of the current parser
|
:param: browse_record of the current parser
|
||||||
:param: result_row_list: [{'key':value}]
|
:param: result_row_list: [{'key':value}]
|
||||||
:param: profile: browserecord of account.statement.profile
|
:param: profile: browserecord of account.statement.profile
|
||||||
:param: statement_id: int/long of the current importing statement ID
|
:param: statement_id: int/long of the current importing \
|
||||||
|
statement ID
|
||||||
:param: context: global context
|
:param: context: global context
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def write_logs_after_import(self, cr, uid, ids, statement_id, num_lines, context):
|
def write_logs_after_import(self, cr, uid, ids, statement_id, num_lines,
|
||||||
"""
|
context):
|
||||||
Write the log in the logger
|
"""Write the log in the logger
|
||||||
|
|
||||||
:param int/long statement_id: ID of the concerned account.bank.statement
|
:param int/long statement_id: ID of the concerned account.bank.statement
|
||||||
:param int/long num_lines: Number of line that have been parsed
|
:param int/long num_lines: Number of line that have been parsed
|
||||||
:return: True
|
:return: True
|
||||||
"""
|
"""
|
||||||
self.message_post(cr,
|
self.message_post(
|
||||||
uid,
|
cr, uid, ids,
|
||||||
ids,
|
body=_('Statement ID %s have been imported with %s '
|
||||||
body=_('Statement ID %s have been imported with %s lines.') %
|
'lines.') % (statement_id, num_lines), context=context)
|
||||||
(statement_id, num_lines),
|
|
||||||
context=context)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Deprecated remove on V8
|
# Deprecated remove on V8
|
||||||
def prepare_statetement_lines_vals(self, *args, **kwargs):
|
def prepare_statetement_lines_vals(self, *args, **kwargs):
|
||||||
return self.prepare_statement_lines_vals(*args, **kwargs)
|
return self.prepare_statement_lines_vals(*args, **kwargs)
|
||||||
|
|
||||||
def prepare_statement_lines_vals(
|
def prepare_statement_lines_vals(self, cr, uid, parser_vals,
|
||||||
self, cr, uid, parser_vals,
|
statement_id, context):
|
||||||
statement_id, context):
|
"""Hook to build the values of a line from the parser returned values.
|
||||||
"""
|
At least it fullfill the statement_id. Overide it to add your own
|
||||||
Hook to build the values of a line from the parser returned values. At
|
completion if needed.
|
||||||
least it fullfill the statement_id. Overide it to add your
|
|
||||||
own completion if needed.
|
|
||||||
|
|
||||||
:param dict of vals from parser for account.bank.statement.line (called by
|
:param dict of vals from parser for account.bank.statement.line \
|
||||||
parser.get_st_line_vals)
|
(called by parser.get_st_line_vals)
|
||||||
:param int/long statement_id: ID of the concerned account.bank.statement
|
:param int/long statement_id: ID of the concerned account.bank.statement
|
||||||
:return: dict of vals that will be passed to create method of statement line.
|
:return: dict of vals that will be passed to create method of \
|
||||||
|
statement line.
|
||||||
"""
|
"""
|
||||||
statement_line_obj = self.pool['account.bank.statement.line']
|
statement_line_obj = self.pool['account.bank.statement.line']
|
||||||
values = parser_vals
|
values = parser_vals
|
||||||
@@ -121,10 +118,8 @@ class AccountStatementProfil(Model):
|
|||||||
values['period_id'] = period_memoizer[date]
|
values['period_id'] = period_memoizer[date]
|
||||||
else:
|
else:
|
||||||
# This is awfully slow...
|
# This is awfully slow...
|
||||||
periods = self.pool.get('account.period').find(cr, uid,
|
periods = self.pool.get('account.period').find(
|
||||||
dt=values.get(
|
cr, uid, dt=values.get('date'), context=context)
|
||||||
'date'),
|
|
||||||
context=context)
|
|
||||||
values['period_id'] = periods[0]
|
values['period_id'] = periods[0]
|
||||||
period_memoizer[date] = periods[0]
|
period_memoizer[date] = periods[0]
|
||||||
values = statement_line_obj._add_missing_default_values(
|
values = statement_line_obj._add_missing_default_values(
|
||||||
@@ -133,8 +128,7 @@ class AccountStatementProfil(Model):
|
|||||||
|
|
||||||
def prepare_statement_vals(self, cr, uid, profile_id, result_row_list,
|
def prepare_statement_vals(self, cr, uid, profile_id, result_row_list,
|
||||||
parser, context=None):
|
parser, context=None):
|
||||||
"""
|
"""Hook to build the values of the statement from the parser and
|
||||||
Hook to build the values of the statement from the parser and
|
|
||||||
the profile.
|
the profile.
|
||||||
"""
|
"""
|
||||||
vals = {'profile_id': profile_id}
|
vals = {'profile_id': profile_id}
|
||||||
@@ -151,9 +145,8 @@ class AccountStatementProfil(Model):
|
|||||||
|
|
||||||
def multi_statement_import(self, cr, uid, ids, profile_id, file_stream,
|
def multi_statement_import(self, cr, uid, ids, profile_id, file_stream,
|
||||||
ftype="csv", context=None):
|
ftype="csv", context=None):
|
||||||
"""
|
"""Create multiple bank statements from values given by the parser for
|
||||||
Create multiple bank statements from values given by the parser for the
|
the given profile.
|
||||||
givenprofile.
|
|
||||||
|
|
||||||
:param int/long profile_id: ID of the profile used to import the file
|
:param int/long profile_id: ID of the profile used to import the file
|
||||||
:param filebuffer file_stream: binary of the providen file
|
:param filebuffer file_stream: binary of the providen file
|
||||||
@@ -162,23 +155,26 @@ class AccountStatementProfil(Model):
|
|||||||
"""
|
"""
|
||||||
prof_obj = self.pool['account.statement.profile']
|
prof_obj = self.pool['account.statement.profile']
|
||||||
if not profile_id:
|
if not profile_id:
|
||||||
raise osv.except_osv(_("No Profile!"),
|
raise orm.except_orm(
|
||||||
_("You must provide a valid profile to import a bank statement!"))
|
_("No Profile!"),
|
||||||
|
_("You must provide a valid profile to import a bank "
|
||||||
|
"statement!"))
|
||||||
prof = prof_obj.browse(cr, uid, profile_id, context=context)
|
prof = prof_obj.browse(cr, uid, profile_id, context=context)
|
||||||
|
|
||||||
parser = new_bank_statement_parser(prof, ftype=ftype)
|
parser = new_bank_statement_parser(prof, ftype=ftype)
|
||||||
res = []
|
res = []
|
||||||
for result_row_list in parser.parse(file_stream):
|
for result_row_list in parser.parse(file_stream):
|
||||||
statement_id = self._statement_import(cr, uid, ids, prof, parser,
|
statement_id = self._statement_import(
|
||||||
file_stream, ftype=ftype, context=context)
|
cr, uid, ids, prof, parser, file_stream, ftype=ftype,
|
||||||
|
context=context)
|
||||||
res.append(statement_id)
|
res.append(statement_id)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _statement_import(self, cr, uid, ids, prof, parser, file_stream, ftype="csv", context=None):
|
def _statement_import(self, cr, uid, ids, prof, parser, file_stream,
|
||||||
"""
|
ftype="csv", context=None):
|
||||||
Create a bank statement with the given profile and parser. It will fullfill the bank statement
|
"""Create a bank statement with the given profile and parser. It will
|
||||||
with the values of the file providen, but will not complete data (like finding the partner, or
|
fullfill the bank statement with the values of the file providen, but
|
||||||
the right account). This will be done in a second step with the completion rules.
|
will not complete data (like finding the partner, or the right account).
|
||||||
|
This will be done in a second step with the completion rules.
|
||||||
|
|
||||||
:param prof : The profile used to import the file
|
:param prof : The profile used to import the file
|
||||||
:param parser: the parser
|
:param parser: the parser
|
||||||
@@ -186,28 +182,25 @@ class AccountStatementProfil(Model):
|
|||||||
:param char: ftype represent the file exstension (csv by default)
|
:param char: ftype represent the file exstension (csv by default)
|
||||||
:return: ID of the created account.bank.statemênt
|
:return: ID of the created account.bank.statemênt
|
||||||
"""
|
"""
|
||||||
statement_obj = self.pool.get('account.bank.statement')
|
statement_obj = self.pool['account.bank.statement']
|
||||||
statement_line_obj = self.pool.get('account.bank.statement.line')
|
statement_line_obj = self.pool['account.bank.statement.line']
|
||||||
attachment_obj = self.pool.get('ir.attachment')
|
attachment_obj = self.pool['ir.attachment']
|
||||||
|
|
||||||
result_row_list = parser.result_row_list
|
result_row_list = parser.result_row_list
|
||||||
# Check all key are present in account.bank.statement.line!!
|
# Check all key are present in account.bank.statement.line!!
|
||||||
if not result_row_list:
|
if not result_row_list:
|
||||||
raise osv.except_osv(_("Nothing to import"),
|
raise orm.except_orm(_("Nothing to import"),
|
||||||
_("The file is empty"))
|
_("The file is empty"))
|
||||||
parsed_cols = parser.get_st_line_vals(result_row_list[0]).keys()
|
parsed_cols = parser.get_st_line_vals(result_row_list[0]).keys()
|
||||||
for col in parsed_cols:
|
for col in parsed_cols:
|
||||||
if col not in statement_line_obj._columns:
|
if col not in statement_line_obj._columns:
|
||||||
raise osv.except_osv(_("Missing column!"),
|
raise orm.except_orm(
|
||||||
_("Column %s you try to import is not "
|
_("Missing column!"),
|
||||||
"present in the bank statement line!") % col)
|
_("Column %s you try to import is not present in the bank "
|
||||||
|
"statement line!") % col)
|
||||||
statement_vals = self.prepare_statement_vals(
|
statement_vals = self.prepare_statement_vals(
|
||||||
cr, uid, prof.id, result_row_list, parser, context)
|
cr, uid, prof.id, result_row_list, parser, context)
|
||||||
statement_id = statement_obj.create(cr, uid,
|
statement_id = statement_obj.create(
|
||||||
statement_vals,
|
cr, uid, statement_vals, context=context)
|
||||||
context=context)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Record every line in the bank statement
|
# Record every line in the bank statement
|
||||||
statement_store = []
|
statement_store = []
|
||||||
@@ -220,7 +213,6 @@ class AccountStatementProfil(Model):
|
|||||||
# Hack to bypass ORM poor perfomance. Sob...
|
# Hack to bypass ORM poor perfomance. Sob...
|
||||||
statement_line_obj._insert_lines(
|
statement_line_obj._insert_lines(
|
||||||
cr, uid, statement_store, context=context)
|
cr, uid, statement_store, context=context)
|
||||||
|
|
||||||
self._write_extra_statement_lines(
|
self._write_extra_statement_lines(
|
||||||
cr, uid, parser, result_row_list, prof, statement_id, context)
|
cr, uid, parser, result_row_list, prof, statement_id, context)
|
||||||
# Trigger store field computation if someone has better idea
|
# Trigger store field computation if someone has better idea
|
||||||
@@ -229,27 +221,24 @@ class AccountStatementProfil(Model):
|
|||||||
start_bal = start_bal['balance_start']
|
start_bal = start_bal['balance_start']
|
||||||
statement_obj.write(
|
statement_obj.write(
|
||||||
cr, uid, [statement_id], {'balance_start': start_bal})
|
cr, uid, [statement_id], {'balance_start': start_bal})
|
||||||
|
|
||||||
attachment_data = {
|
attachment_data = {
|
||||||
'name': 'statement file',
|
'name': 'statement file',
|
||||||
'datas': file_stream,
|
'datas': file_stream,
|
||||||
'datas_fname': "%s.%s" % (datetime.datetime.now().date(), ftype),
|
'datas_fname': "%s.%s" % (datetime.datetime.now().date(),
|
||||||
|
ftype),
|
||||||
'res_model': 'account.bank.statement',
|
'res_model': 'account.bank.statement',
|
||||||
'res_id': statement_id,
|
'res_id': statement_id,
|
||||||
}
|
}
|
||||||
attachment_obj.create(cr, uid, attachment_data, context=context)
|
attachment_obj.create(cr, uid, attachment_data, context=context)
|
||||||
|
|
||||||
# If user ask to launch completion at end of import, do it!
|
# If user ask to launch completion at end of import, do it!
|
||||||
if prof.launch_import_completion:
|
if prof.launch_import_completion:
|
||||||
statement_obj.button_auto_completion(
|
statement_obj.button_auto_completion(
|
||||||
cr, uid, [statement_id], context)
|
cr, uid, [statement_id], context)
|
||||||
|
|
||||||
# Write the needed log infos on profile
|
# Write the needed log infos on profile
|
||||||
self.write_logs_after_import(cr, uid, prof.id,
|
self.write_logs_after_import(cr, uid, prof.id,
|
||||||
statement_id,
|
statement_id,
|
||||||
len(result_row_list),
|
len(result_row_list),
|
||||||
context)
|
context)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
error_type, error_value, trbk = sys.exc_info()
|
error_type, error_value, trbk = sys.exc_info()
|
||||||
st = "Error: %s\nDescription: %s\nTraceback:" % (
|
st = "Error: %s\nDescription: %s\nTraceback:" % (
|
||||||
@@ -260,6 +249,6 @@ class AccountStatementProfil(Model):
|
|||||||
# For now we avoid re-catching error in debug mode
|
# For now we avoid re-catching error in debug mode
|
||||||
if config['debug_mode']:
|
if config['debug_mode']:
|
||||||
raise
|
raise
|
||||||
raise osv.except_osv(_("Statement import error"),
|
raise orm.except_orm(_("Statement import error"),
|
||||||
_("The statement cannot be created: %s") % st)
|
_("The statement cannot be created: %s") % st)
|
||||||
return statement_id
|
return statement_id
|
||||||
|
|||||||
@@ -22,11 +22,10 @@
|
|||||||
import base64
|
import base64
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from openerp.tests import common
|
from openerp.tests import common
|
||||||
|
|
||||||
|
|
||||||
class test_coda_import(common.TransactionCase):
|
class TestCodaImport(common.TransactionCase):
|
||||||
|
|
||||||
def prepare(self):
|
def prepare(self):
|
||||||
self.company_a = self.browse_ref('base.main_company')
|
self.company_a = self.browse_ref('base.main_company')
|
||||||
@@ -36,7 +35,6 @@ class test_coda_import(common.TransactionCase):
|
|||||||
# create the 2009 fiscal year since imported coda file reference
|
# create the 2009 fiscal year since imported coda file reference
|
||||||
# statement lines in 2009
|
# statement lines in 2009
|
||||||
self.fiscalyear_id = self._create_fiscalyear("2011", self.company_a.id)
|
self.fiscalyear_id = self._create_fiscalyear("2011", self.company_a.id)
|
||||||
|
|
||||||
self.account_id = self.ref("account.a_recv")
|
self.account_id = self.ref("account.a_recv")
|
||||||
self.journal_id = self.ref("account.bank_journal")
|
self.journal_id = self.ref("account.bank_journal")
|
||||||
self.import_wizard_obj = self.registry('credit.statement.import')
|
self.import_wizard_obj = self.registry('credit.statement.import')
|
||||||
@@ -77,7 +75,8 @@ class test_coda_import(common.TransactionCase):
|
|||||||
self.cr, self.uid, wizard_id)
|
self.cr, self.uid, wizard_id)
|
||||||
statement_id = self.account_bank_statement_obj.search(
|
statement_id = self.account_bank_statement_obj.search(
|
||||||
self.cr, self.uid, eval(res['domain']))
|
self.cr, self.uid, eval(res['domain']))
|
||||||
return self.account_bank_statement_obj.browse(self.cr, self.uid, statement_id)[0]
|
return self.account_bank_statement_obj.browse(
|
||||||
|
self.cr, self.uid, statement_id)[0]
|
||||||
|
|
||||||
def test_simple_xls(self):
|
def test_simple_xls(self):
|
||||||
"""Test import from xls
|
"""Test import from xls
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ class CreditPartnerStatementImporter(orm.TransientModel):
|
|||||||
if context is None:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
res = {}
|
res = {}
|
||||||
if (context.get('active_model', False) == 'account.statement.profile' and
|
if (context.get('active_model', False) ==
|
||||||
|
'account.statement.profile' and
|
||||||
context.get('active_ids', False)):
|
context.get('active_ids', False)):
|
||||||
ids = context['active_ids']
|
ids = context['active_ids']
|
||||||
assert len(
|
assert len(
|
||||||
@@ -57,8 +58,8 @@ class CreditPartnerStatementImporter(orm.TransientModel):
|
|||||||
'journal_id': fields.many2one('account.journal',
|
'journal_id': fields.many2one('account.journal',
|
||||||
'Financial journal to use transaction'),
|
'Financial journal to use transaction'),
|
||||||
'file_name': fields.char('File Name', size=128),
|
'file_name': fields.char('File Name', size=128),
|
||||||
'receivable_account_id': fields.many2one('account.account',
|
'receivable_account_id': fields.many2one(
|
||||||
'Force Receivable/Payable Account'),
|
'account.account', 'Force Receivable/Payable Account'),
|
||||||
'force_partner_on_bank': fields.boolean(
|
'force_partner_on_bank': fields.boolean(
|
||||||
'Force partner on bank move',
|
'Force partner on bank move',
|
||||||
help="Tic that box if you want to use the credit insitute partner "
|
help="Tic that box if you want to use the credit insitute partner "
|
||||||
@@ -73,12 +74,12 @@ class CreditPartnerStatementImporter(orm.TransientModel):
|
|||||||
def onchange_profile_id(self, cr, uid, ids, profile_id, context=None):
|
def onchange_profile_id(self, cr, uid, ids, profile_id, context=None):
|
||||||
res = {}
|
res = {}
|
||||||
if profile_id:
|
if profile_id:
|
||||||
c = self.pool.get("account.statement.profile").browse(
|
c = self.pool["account.statement.profile"].browse(
|
||||||
cr, uid, profile_id, context=context)
|
cr, uid, profile_id, context=context)
|
||||||
res = {'value':
|
res = {'value':
|
||||||
{'partner_id': c.partner_id and c.partner_id.id or False,
|
{'partner_id': c.partner_id and c.partner_id.id or False,
|
||||||
'journal_id': c.journal_id and c.journal_id.id or False,
|
'journal_id': c.journal_id and c.journal_id.id or False,
|
||||||
'receivable_account_id': c.receivable_account_id and c.receivable_account_id.id or False,
|
'receivable_account_id': c.receivable_account_id.id,
|
||||||
'force_partner_on_bank': c.force_partner_on_bank,
|
'force_partner_on_bank': c.force_partner_on_bank,
|
||||||
'balance_check': c.balance_check,
|
'balance_check': c.balance_check,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user