[IMP] Rebuild of all bank statment stuffs. This commit is more a backup cause it is under hard devs V2.0

(lp:c2c-financial-addons/6.1 rev 24.1.18)
This commit is contained in:
Joël Grand-Guillaume
2012-06-14 16:21:57 +02:00
parent 6360ac7369
commit 57215e0aa1
25 changed files with 1163 additions and 270 deletions

View File

@@ -29,13 +29,19 @@
'description': """
The goal of this module is to improve the basic bank statement, help dealing with huge volume of
reconciliation by providing basic rules to identify the partner of a bank statement line.
It will also take care of the chosen profile to make his work.
His goal is to provide an easy way to fullfill the info of a bank statement line based on rules.
The reference of the line is always used by the reconciliation process. We're supposed to copy
there (or write manually) the matching string. That can be : the order Number or an invoice number,
or anything that will be found in the invoice entry part to make the match.
""",
'website': 'http://www.camptocamp.com',
'init_xml': [],
'update_xml': [
'statement_view.xml',
'data.xml',
],
'demo_xml': [],
'test': [],

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="bank_statement_completion_rule_2" model="account.statement.completion.rule">
<field name="name">Match from line label (based on partner field 'Bank Statement Label')</field>
<field name="sequence">60</field>
<field name="function_to_call">get_from_label_and_partner_field</field>
</field>
</record>
<record id="bank_statement_completion_rule_3" model="account.statement.completion.rule">
<field name="name">Match from line label (based on partner name)</field>
<field name="sequence">70</field>
<field name="function_to_call">get_from_label_and_partner_name</field>
</field>
</record>
</data>
</openerp>

View File

@@ -51,32 +51,32 @@ class res_partner(osv.osv):
# return partner_id and partner_id[0]
# return False
def get_partner_from_label_based_on_bank_statement_label(self, cr, uid, label, context=None):
ids = self.search(cr, uid, [['bank_statement_label', '!=', False]], context=context)
for partner in self.browse(cr, uid, ids, context=context):
for partner_label in partner.bank_statement_label.split(';'):
if partner_label in label:
return partner.id
return False
def get_supplier_partner_from_label_based_on_name(self, cr, uid, label, context=None):
supplier_ids = self.search(cr, uid, [['supplier', '=', True]], context=context)
for partner in self.browse(cr, uid, supplier_ids, context=context):
if partner.name in label:
return partner.id
return False
def get_partner_account(self, cr, uid, id, amount, context=None):
partner = self.browse(cr, uid, id, context=context)
if partner.supplier and not partner.customer:
return partner.property_account_payable.id
if partner.customer and not partner.supplier:
return partner.property_account_receivable.id
if amount >0:
return partner.property_account_receivable.id
else:
return partner.property_account_payable.id
# def get_partner_from_label_based_on_bank_statement_label(self, cr, uid, label, context=None):
# ids = self.search(cr, uid, [['bank_statement_label', '!=', False]], context=context)
# for partner in self.browse(cr, uid, ids, context=context):
# for partner_label in partner.bank_statement_label.split(';'):
# if partner_label in label:
# return partner.id
# return False
#
# def get_supplier_partner_from_label_based_on_name(self, cr, uid, label, context=None):
# supplier_ids = self.search(cr, uid, [['supplier', '=', True]], context=context)
# for partner in self.browse(cr, uid, supplier_ids, context=context):
# if partner.name in label:
# return partner.id
# return False
#
# def get_partner_account(self, cr, uid, id, amount, context=None):
# partner = self.browse(cr, uid, id, context=context)
# if partner.supplier and not partner.customer:
# return partner.property_account_payable.id
# if partner.customer and not partner.supplier:
# return partner.property_account_receivable.id
#
# if amount >0:
# return partner.property_account_receivable.id
# else:
# return partner.property_account_payable.id

View File

@@ -23,6 +23,14 @@ import netsvc
logger = netsvc.Logger()
from openerp.osv.orm import Model, fields
class ErrorTooManyPartner(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
class AccountStatementProfil(Model):
_inherit = "account.statement.profil"
@@ -32,68 +40,124 @@ class AccountStatementProfil(Model):
# 'transferts_account_id':fields.many2one('account.account', 'Transferts Account'),
'rule_ids':fields.many2many('account.statement.completion.rule',
string='Related statement profiles',
rel='account_statement_rule_statement_profile_to_rel',
'profile_id','rule_id',
'Related statement profiles'),
ids1='profile_id',ids2='rule_id',
),
}
def find_partner_by_rules(self, cr, uid, ids, field_value, context=None):
def find_values_from_rules(self, cr, uid, ids, line_id, context=None):
"""This method will execute all rules, in their sequence order,
to match a partner for the given statement.line and return his id."""
to match a partner for the given statement.line and return his id.
:param int/long line_id: eee
:return: A dict of value that can be passed directly to the write method of
the statement line:
{'partner_id': value,
'account_id' : value,
...
}
"""
if not context:
context={}
partner_id = False
res = {}
for profile in self.browse(cr, uid, ids, context=context):
for rule in profile.rule_ids:
method_to_call = getattr(rule, rule.function_to_call)
partner_id = method_to_call(cr,uid,field_value,context)
if partner_id:
return partner_id
return partner_id
result = method_to_call(cr,uid,line_id,context)
if result:
return res
return res
class AccountStatementCompletionRule(Model):
"""This will represent all the completion method that we can have to
fullfill the bank statement. You'll be able to extend them in you own module
and choose those to apply for every statement profile."""
and choose those to apply for every statement profile.
The goal of a rules is to fullfill at least the partner of the line, but
if possible also the reference because we'll use it in the reconciliation
process. The reference should contain the invoice number or the SO number
"""
_name = "account.statement.completion.rule"
_order = "sequence asc"
_get_functions = [('get_from_label_and_partner_field', 'From line label (based on partner field)'),\
('in', 'External -> OpenERP'), ('out', 'External <- OpenERP')]
def _get_functions(self):
"""List of available methods for rules. Override this to add you own."""
return [
('get_from_label_and_partner_field', 'From line label (based on partner field)'),
('get_from_label_and_partner_name', 'From line label (based on partner name)'),
]
_columns={
'sequence': fields.integer('Sequence'),
'name': fields.char('Name')
'profile_ids':fields.many2many('account.statement.profil',
'sequence': fields.integer('Sequence', help="Lower means paresed first."),
'name': fields.char('Name'),
'profile_ids': fields.many2many('account.statement.profil',
rel='account_statement_rule_statement_profile_to_rel',
'rule_id', 'profile_id',
'Related statement profiles'),
'function_to_call': fields.selection(_get_functions, 'Type'),
ids1='rule_id', ids2='profile_id',
string='Related statement profiles'),
'function_to_call': fields.selection(_get_functions, 'Method'),
}
def get_from_label_and_partner_field(self, cr, uid, field_value, context=None):
def get_from_label_and_partner_field(self, cr, uid, line_id, context=None):
"""Match the partner based on the label field of the statement line
and the text defined in the 'bank_statement_label' field of the partner.
Remember that we can have values separated with ;"""
Remember that we can have values separated with ; Then, call the generic
st_line method to complete other values.
If more than one partner matched, raise an error.
Return:
A dict of value that can be passed directly to the write method of
the statement line.
{'partner_id': value,
'account_id' : value,
...}
"""
partner_obj = self.pool.get('res.partner')
ids = partner_obj.search(cr, uid, [['bank_statement_label', '!=', False]], context=context)
for partner in self.browse(cr, uid, ids, context=context):
for partner_label in partner.bank_statement_label.split(';'):
if partner_label in field_value:
return partner.id
return False
st_obj = self.pool.get('account.bank.statement.line')
st_line = st_obj.browse(cr,uid,line_id)
res = {}
compt = 0
if st_line:
ids = partner_obj.search(cr, uid, [['bank_statement_label', '!=', False]], context=context)
for partner in self.browse(cr, uid, ids, context=context):
for partner_label in partner.bank_statement_label.split(';'):
if partner_label in st_line.label:
compt += 1
res['partner_id'] = partner.id
if compt > 1:
raise ErrorTooManyPartner(_('Line named "%s" was matched by more than one partner.')%(st_line.name,st_line.id))
st_vals = st_obj.get_values_for_line(cr, uid, profile_id = st_line.statement_id.profile_id.id,
partner_id = res.get('partner_id',False), line_type = st_line.type, amount = st_line.amount, context = context)
res.update(st_vals)
return res
def get_from_label_and_partner_name(self, cr, uid, field_value, context=None):
def get_from_label_and_partner_name(self, cr, uid, line_id, context=None):
"""Match the partner based on the label field of the statement line
and the name of the partner."""
supplier_ids = self.search(cr, uid, [['supplier', '=', True]], context=context)
sql = """SELECT id FROM res_partner WHERE name ilike """
for partner in self.browse(cr, uid, supplier_ids, context=context):
if partner.name in label:
return partner.id
return False
and the name of the partner.
Then, call the generic st_line method to complete other values.
Return:
A dict of value that can be passed directly to the write method of
the statement line.
{'partner_id': value,
'account_id' : value,
...}
"""
res = {}
st_obj = self.pool.get('account.bank.statement.line')
st_line = st_obj.browse(cr,uid,line_id)
if st_line:
sql = "SELECT id FROM res_partner WHERE name ~* '.*%s.*'"
cr.execute(sql, (st_line.label,))
result = cr.fetchall()
if len(result) > 1:
raise ErrorTooManyPartner(_('Line named "%s" was matched by more than one partner.')%(st_line.name,st_line.id))
for id in result:
res['partner_id'] = id
st_vals = st_obj.get_values_for_line(cr, uid, profile_id = st_line.statement_id.profile_id.id,
partner_id = res.get('partner_id',False), line_type = st_line.type, st_line.amount, context)
res.update(st_vals)
return res
class AccountStatementLine(Model):
@@ -114,77 +178,100 @@ class AccountStatementLine(Model):
}
def auto_complete_line(self, cr, uid, line, context=None):
def get_line_values_from_rules(self, cr, uid, ids, context=None):
"""
We'll try to find out the values related to the line based on what we
have and rules setted on the profile..
"""
profile_obj = self.pool.get('account.statement.profil')
res={}
if not line.partner_id or line.account_id.id ==1:
partner_obj = self.pool.get('res.partner')
partner_id=False
if line.order_ref:
partner_id = partner_obj.get_partner_from_order_ref(cr, uid, line.order_ref, context=context)
if not partner_id and line.email_address:
partner_id = partner_obj.get_partner_from_email(cr, uid, line.email_address, context=context)
if not partner_id and line.partner_name:
partner_id = partner_obj.get_partner_from_name(cr, uid, line.partner_name, context=context)
if not partner_id and line.label:
partner_id = partner_obj.get_partner_from_label_based_on_bank_statement_label(cr, uid, line.label, context=context)
if partner_id:
res = {'partner_id': partner_id}
if context['auto_completion']:
#Build the space for expr
space = {
'self':self,
'cr':cr,
'uid':uid,
'line': line,
'res': res,
'context':context,
}
exec context['auto_completion'] in space
if space.get('result', False):
res.update(space['result'])
errors_stack = []
for line in self.browse(cr,uid, ids, context):
try:
vals = profile_obj.find_values_from_rules(cr, uid, ids, line.id, context)
res[line.id]=vals
except ErrorTooManyPartner, exc:
msg = "Line ID %s had following error: %s" % (line.id, str(exc))
errors_stack.append(msg)
# if not auto_complete_line
# if not line.partner_id or line.account_id.id ==1:
# partner_obj = self.pool.get('res.partner')
# partner_id=False
# if line.order_ref:
# partner_id = partner_obj.get_partner_from_order_ref(cr, uid, line.order_ref, context=context)
# if not partner_id and line.email_address:
# partner_id = partner_obj.get_partner_from_email(cr, uid, line.email_address, context=context)
# if not partner_id and line.partner_name:
# partner_id = partner_obj.get_partner_from_name(cr, uid, line.partner_name, context=context)
# if not partner_id and line.label:
# partner_id = partner_obj.get_partner_from_label_based_on_bank_statement_label(cr, uid, line.label, context=context)
# if partner_id:
# res = {'partner_id': partner_id}
# if context['auto_completion']:
# #Build the space for expr
# space = {
# 'self':self,
# 'cr':cr,
# 'uid':uid,
# 'line': line,
# 'res': res,
# 'context':context,
# }
# exec context['auto_completion'] in space
# if space.get('result', False):
# res.update(space['result'])
if errors_stack:
msg = u"\n".join(errors_stack)
raise ErrorTooManyPartner(msg)
return res
class A(object):
def xx_toto():
print 'toto'
a = A()
funcs = ['yy_toto', 'xx_toto']
for i in funcs:
if hasattr(a, i):
to_call = getattr(a, i)
to_call()
else:
raise NameError('blblblb')
#
# class A(object):
# def xx_toto():
# print 'toto'
#
#
# a = A()
# funcs = ['yy_toto', 'xx_toto']
# for i in funcs:
# if hasattr(a, i):
# to_call = getattr(a, i)
# to_call()
# else:
# raise NameError('blblblb')
class AccountBankSatement(Model):
"""We add a basic button and stuff to support the auto-completion
of the bank statement once line have been imported or manually entred.
"""
"""
We add a basic button and stuff to support the auto-completion
of the bank statement once line have been imported or manually entred.
"""
_inherit = "account.bank.statement"
def button_auto_completion(self, cr, uid, ids, context=None):
if not context:
context={}
stat_line_obj = self.pool.get('account.bank.statement.line')
errors_msg=False
for stat in self.browse(cr, uid, ids, context=context):
ctx = context.copy()
if stat.bank_statement_import_id:
ctx['partner_id'] = stat.bank_statement_import_id.partner_id.id
ctx['transferts_account_id'] = stat.bank_statement_import_id.transferts_account_id.id
ctx['credit_account_id'] = stat.bank_statement_import_id.credit_account_id.id
ctx['fee_account_id'] = stat.bank_statement_import_id.fee_account_id.id
ctx['auto_completion'] = stat.bank_statement_import_id.auto_completion
for line in stat.line_ids:
vals = stat_line_obj.auto_complete_line(cr, uid, line, context=ctx)
if not line.ref and not vals.get('ref', False):
vals['ref'] = stat.name
line_ids = map(lambda x:x.id, stat.line_ids)
try:
res = stat_line_obj.get_line_values_from_rules(cr, uid, line_ids, context=ctx)
except ErrorTooManyPartner, exc:
errors_msg = str(exc)
for id in line_ids:
vals = res[line.id]
if vals:
stat_line_obj.write(cr, uid, line.id, vals, context=ctx)
stat_line_obj.write(cr, uid, id, vals, context=ctx)
# cr.commit()
# TOTEST: I don't know if this is working...
if errors_msg:
# raise osv.except_osv(_('Error'), errors_msg)
warning = {
'title': _('Error!'),
'message' : errors_msg,
}
return {'warning': warning}
return True
def auto_confirm(self, cr, uid, ids, context=None):

View File

@@ -18,5 +18,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import parser
import wizard
import statement

View File

@@ -19,7 +19,7 @@
#
##############################################################################
{'name': "Bank statement easy import",
{'name': "Bank statement base import",
'version': '1.0',
'author': 'Camptocamp',
'maintainer': 'Camptocamp',
@@ -27,9 +27,30 @@
'complexity': 'normal', #easy, normal, expert
'depends': ['account_statement_ext','account_statement_base_completion'],
'description': """
The goal of this module is bring basic method and fields on bank statement to deal with
This module bring 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 example that provide a basic way of importing
bank statement through a standard .csv or .xml file is providen.
The goal is here to populate the statement lines of a bank statement with the infos that the
bank or offic give you. Then, if you need to complete data from there, add your own
statement_*_completion module and implement the needed rules.
This module improves the bank statement and allow you to import your bank transactions with
a standard .csv or .xls file (you'll find it in the 'datas' folder). It'll respect the profil
you'll choose (providen by the accouhnt_statement_ext module) to pass the entries.
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
in the generated entries and will be useful for reconciliation process
* date : date of the payment
* amount : amount paid in the currency of the journal used in the importation profil
* commission_amount : amount of the comission for each line
* label : the comunication given by the payment office, used as communication in the
generated entries.
""",
'website': 'http://www.camptocamp.com',

View File

@@ -0,0 +1,4 @@
"ref";"date";"amount";"commission_amount";"label"
50969286;2011-03-07 13:45:14;118.4;-11.84;"label a"
51065326;2011-03-05 13:45:14;189;-15.12;"label b"
51179306;2011-03-02 17:45:14;189;-15.12;"label c"
1 ref date amount commission_amount label
2 50969286 2011-03-07 13:45:14 118.4 -11.84 label a
3 51065326 2011-03-05 13:45:14 189 -15.12 label b
4 51179306 2011-03-02 17:45:14 189 -15.12 label c

Binary file not shown.

View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Nicolas Bessi
# Copyright 2011-2012 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from parser import new_bank_statement_parser
from parser import BankStatementImportParser
import file_parser
import generic_file_parser

View File

@@ -0,0 +1,141 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright Camptocamp SA
# Author Nicolas Bessi, Joel Grand-Guillaume
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.tools.translate import _
import base64
import csv
import tempfile
import datetime
from . import parser
try:
import xlrd
except:
raise Exception(_('Please install python lib xlrd'))
class FileParser(BankStatementImportParser):
"""Abstract clall for that help to build a specific parser for all
.csv and .xls files"""
def __init__(self, parse_name=None, keys_to_validate={}, ftype='csv', convertion_dict=None, *args, **kwargs):
"""
:param: convertion_dict : keys and type to convert of every column in the file like
{
'ref': unicode,
'label': unicode,
'date': datetime.datetime,
'amount': float,
'commission_amount': float
}
"""
super(self,FileParser).__init__(parse_name, *args, **kwargs)
if ftype in ('csv', 'xls'):
self.ftype = ftype
else:
raise Exception(_('Invalide file type %s. please use csv or xls') % (ftype))
self.keys_to_validate = keys_to_validate
self.convertion_dict = convertion_dict
def _custom_format(self, *args, **kwargs):
return True
def _pre(self, *args, **kwargs):
return True
def _validate(self, *args, **kwargs):
parsed_cols = self.result_row_list[0].keys()
for col in self.keys_to_validate:
if col not in parsed_cols:
raise Exception(_('Column %s not present in file') % (col))
return True
def _post(self, *args, **kwargs):
"""Cast row type depending on the file format .csv or .xls"""
self.result_row_list = self._cast_rows(kwargs)
return True
def _parse(self, *args, **kwargs):
"""Launch the parsing through .csv or .xls depending on the
given ftype"""
res = None
if self.ftype == 'csv':
res = self._parse_csv()
else:
res = self._parse_xls()
self.result_row_list = res
return True
def _parse_csv(self, delimiter=';'):
"return an array of dict from csv file"
csv_file = tempfile.NamedTemporaryFile()
csv_file.write(self.filebuffer)
# We ensure that cursor is at beginig of file
csv_file.seek(0)
reader = UnicodeDictReader(
open(csv_file.name).readlines(),
delimiter=delimiter
)
return [x for x in reader]
def _parse_xls(self):
"return an array of dict from xls file"
wb_file = tempfile.NamedTemporaryFile()
wb_file.write(self.filebuffer)
# We ensure that cursor is at beginig of file
wb_file.seek(0)
wb = xlrd.open_workbook(wb_file.name)
sheet = wb.sheet_by_index(0)
header = sheet.row_values(0)
res = []
for rownum in range(1, sheet.nrows):
res.append(dict(zip(header, sheet.row_values(rownum))))
try:
wb_file.close()
except Exception, e:
pass #file is allready closed
return res
def _from_csv(self, result_set, conversion_rules):
for line in result_set:
for rule in conversion_rules:
if conversion_rules[rule] == datetime.datetime:
date_string = line[rule].split(' ')[0]
line[rule] = datetime.datetime.strptime(date_string,
'%Y-%m-%d')
else:
line[rule] = conversion_rules[rule](line[rule])
return result_set
def _from_xls(self, result_set, conversion_rules):
for line in result_set:
for rule in conversion_rules:
if conversion_rules[rule] == datetime.datetime:
t_tuple = xlrd.xldate_as_tuple(line[rule], 1)
line[rule] = datetime.datetime(*t_tuple)
else:
line[rule] = conversion_rules[rule](line[rule])
return result_set
def _cast_rows(self):
func = getattr(self, '_from_%s'%(self.ftype))
res = func(self.result_row_list, self.convertion_dict)
return res

View File

@@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright Camptocamp SA
# Author Joel Grand-Guillaume
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.tools.translate import _
import base64
import csv
import tempfile
import datetime
from . import file_parser
try:
import xlrd
except:
raise Exception(_('Please install python lib xlrd'))
class GenericFileParser(FileParser):
"""Generic parser that use a define format in csv or xls to import
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."""
def __init__(self, parse_name = None, ftype='csv'):
convertion_dict = {
'ref': unicode,
'label': unicode,
'date': datetime.datetime,
'amount': float,
'commission_amount': float
}
# Order of cols does not matter but first row of the file has to be header
keys_to_validate = ['ref', 'label', 'date', 'amount', 'commission_amount']
super(self,GenericFileParser).__init__(parser_for = parse_name, keys_to_validate={}, ftype='csv', convertion_dict=None ):
@classmethod
def parser_for(cls, parser_name):
return parser_name == 'generic_csvxls_so'

View File

@@ -0,0 +1,151 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Joel Grand-Guillaume
# Copyright 2011-2012 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import base64
def UnicodeDictReader(utf8_data, **kwargs):
csv_reader = csv.DictReader(utf8_data, **kwargs)
for row in csv_reader:
yield dict([(key, unicode(value, 'utf-8')) for key, value in row.iteritems()])
class BankStatementImportParser(object):
"""Abstract class for defining parser for different files and
format to import in a bank statement"""
def __init__(self, parser_name = None, *args, **kwargs):
# The name of the parser as it will be called
self.parser_name = parser_name
# The result as a list of row
self.result_row_list = None
# The file buffer on which to work on
self.filebuffer = None
# Concatenate here the global commission taken by the bank/office
# for this statement.
self.commission_global_amount = None
@classmethod
def parser_for(cls, parser_name):
return False
def _decode_64b_stream(self):
self.filebuffer = base64.b64decode(self.filebuffer)
return True
def _format(self, decode_base_64=True, **kwargs):
if decode_base_64:
self._decode_64b_stream()
self._custom_format(kwargs)
return True
def _custom_format(self, *args, **kwargs):
"""Implement a method to convert format, encoding and so on before
starting to work on datas."""
return NotImplementedError
def _pre(self, *args, **kwargs):
"""Implement a method to make a pre-treatment on datas before parsing
them, like concatenate stuff, and so..."""
return NotImplementedError
def _validate(self, *args, **kwargs):
"""Implement a method to validate the self.result_row_list instance
property and raise an error if not valid."""
return NotImplementedError
def _post(self, *args, **kwargs):
"""Implement a method to make some last changes on the result of parsing
the datas, like converting dates, computing commission, ... """
return NotImplementedError
def _parse(self, *args, **kwargs):
"""Implement a method to save the result of parsing self.filebuffer
in self.result_row_list instance property. Put the commission global
amount in the self.commission_global_amount one."""
return NotImplementedError
def parse(self, filebuffer, *args, **kwargs):
"""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
that need to be define for each parser.
Return:
[] of rows as {'key':value}
Note: The row_list must contain only value that are present in the account.
bank.statement.line object !!!
"""
if filebuffer:
self.filebuffer = filebuffer
else:
raise Exception(_('No buffer file given.'))
self._format(args, kwargs)
self._pre(args, kwargs)
self._parse(args, kwargs)
self._validate(args, kwargs)
self._post(args, kwargs)
return self.result_row_list,
def itersubclasses(cls, _seen=None):
"""
itersubclasses(cls)
Generator over all subclasses of a given class, in depth first order.
>>> list(itersubclasses(int)) == [bool]
True
>>> class A(object): pass
>>> class B(A): pass
>>> class C(A): pass
>>> class D(B,C): pass
>>> class E(D): pass
>>>
>>> for cls in itersubclasses(A):
... print(cls.__name__)
B
D
E
C
>>> # get ALL (new-style) classes currently defined
>>> [cls.__name__ for cls in itersubclasses(object)] #doctest: +ELLIPSIS
['type', ...'tuple', ...]
"""
if not isinstance(cls, type):
raise TypeError('itersubclasses must be called with '
'new-style classes, not %.100r' % cls)
if _seen is None: _seen = set()
try:
subs = cls.__subclasses__()
except TypeError: # fails only when cls is type
subs = cls.__subclasses__(cls)
for sub in subs:
if sub not in _seen:
_seen.add(sub)
yield sub
for sub in itersubclasses(sub, _seen):
yield sub
def new_bank_statement_parser(parser_name, *args, **kwargs):
for cls in itersubclasses(BankStatementImportParser):
if cls.parser_for(parser_name):
return cls(parser_name, *args, **kwargs)
raise ValueError

View File

@@ -24,58 +24,161 @@ import datetime
import netsvc
logger = netsvc.Logger()
from openerp.osv.orm import Model, fields
# from account_statement_base_import.parser.file_parser import FileParser
from parser import new_bank_statement_parser
class AccountStatementProfil(Model):
_inherit = "account.statement.profil"
def get_type_selection(self, cr, uid, context=None):
"""
Has to be inherited to add parser
"""
return [('generic_csvxls_so', 'Generic .csv/.xls based on SO Name')]
_columns = {
'launch_import_completion': fields.boolean("Launch completion after import",
help="Tic that box to automatically launch the completion on each imported\
file using this profile."),
'last_import_date': fields.date("Last Import Date"),
'last_import_date': fields.datetime("Last Import Date"),
'rec_log': fields.text('log', readonly=True),
'import_type': fields.selection(get_import_type_selection, 'Type of import', required=True,
help = "Choose here the method by which you want to import bank statement for this profil."),
}
def launch_import_bank_statement(self, cr, uid, ids, context=None):
stat_obj = self.pool.get('account.bank.statement')
def write_logs_after_import(self, cr, uid, ids, statement_id, num_lines, context):
for id in ids:
logger = netsvc.Logger()
res = self.action_import_bank_statement(cr, uid, id, conteaccount_xt)
#autocomplete bank statement
stat_obj.button_auto_completion(cr, uid, res['crids'], context=context)
stat_obj.auto_confirm(cr, uid, res['crids'], context=context)
log = self.read(cr, uid, id, ['rec_log'], context=context)['rec_log']
log_line = log and log.split("\n") or []
log_line[0:0] = [datetime.now().strftime('%Y-%m-%d %H:%M:%S') + ' : ' + str(len(res['crids'])) + _(' bank statement have been imported and ' + str(len(res['exist_ids'])) + _(' bank statement already exist'))]
import_date = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
log_line[0:0] = [import_date + ' : '
+ _("Bank Statement ID %s have been imported with %s lines ") %(statement_id, num_lines)]
log = "\n".join(log_line)
self.write(cr, uid, id, {'rec_log' : log}, context=context)
logger.notifyChannel('banck statement import', netsvc.LOG_INFO, "%s bank statement have been imported and %s bank statement already exist"%(len(res['crids']), len(res['exist_ids'])))
self.write(cr, uid, id, {'rec_log' : log, 'last_import_date':import_date}, context=context)
logger.notifyChannel('Bank Statement Import', netsvc.LOG_INFO,
"Bank Statement ID %s have been imported with %s lines "%(statement_id, num_lines))
return True
def action_import_bank_statement(self, cr, uid, id, context=None):
'''not implemented in this module'''
return {}
def statement_import(self, cursor, uid, ids, profile_id, file_stream, ftype="csv", context=None):
"""Create a bank statement with the given profile and parser. It will fullfill the bank statement
with the values of the file providen, but will not complete data (like finding the partner, or
the right account). This will be done in a second step with the completion rules.
"""
context = context or {}
statement_obj = self.pool.get('account.bank.statement')
statement_line_obj = self.pool.get('account.bank.statement.line')
attachment_obj = self.pool.get('ir.attachment')
prof_obj = self.pool.get("account.statement.profil")
if not profile_id:
raise osv.except_osv(
_("No Profile !"),
_("You must provide a valid profile to import a bank statement !"))
else:
prof = prof_obj.browse(cursor,uid,profile_id,context)
partner_id = prof.partner_id and prof.partner_id.id or False
commission_account_id = prof.commission_account_id and prof.commission_account_id.id or False
commission_analytic_id = prof.commission_analytic_id and prof.commission_analytic_id.id or False
def open_bank_statement(self, cr, uid, ids, context):
task = self.browse(cr, uid, ids, context=context)[0]
parser = new_bank_statement_parser(parse_name=prof.import_type, ftype=ftype)
result_row_list = parser.parse(file_stream)
return {
'name': 'Bank ',
'view_type': 'form',
'view_mode': 'form',
'view_id': [252],
'res_model': self._name,
'type': 'ir.actions.act_window',
'nodestroy': True,
'target': 'current',
'res_id': self.read(cr, uid, ids, ['bank_statement_ids'],context=context)[0]['bank_statement_ids'],
}
# Check all key are present in account.bank.statement.line !!
parsed_cols = self.result_row_list[0].keys()
for col in parsed_cols:
if col not in statement_line_obj.__columns__:
raise osv.except_osv(
_("Missing column !"),
_("Column %s you try to import is not present in the bank statement line !") %(col))
class AccountBankSatement(Model):
statement_id = statement_obj.create(cursor,uid,{'profile_id':prof.id,},context)
account_receivable, account_payable = self.get_default_pay_receiv_accounts(cursor, uid, context)
commission_global_amount = 0.0
try:
# Record every line in the bank statement and compute the global commission
# based on the commission_amount column
for line in result_row_list:
line_partner_id = False
line_to_reconcile = False
_inherit = "account.bank.statement"
commission_global_amount += line.get('commission_amount', 0.0)
values = {
'name': line.get('label', line.get('ref','/')),
'date': line.get('date', datetime.datetime.now().date()),
'amount': line['amount'],
'ref': line['ref'],
'type': 'customer',
'statement_id': statement_id,
#'account_id': journal.default_debit_account_id
}
values['account_id'] = self.get_account_for_counterpart(
cursor,
uid,
line['amount'],
account_receivable,
account_payable
)
# we finally create the line in system
statement_line_obj.create(cursor, uid, values, context=context)
# we create commission line
if commission_global_amount:
comm_values = {
'name': 'IN '+ _('Commission line'),
'date': datetime.datetime.now().date(),
'amount': commission_global_amount,
'partner_id': partner_id,
'type': 'general',
'statement_id': statement_id,
'account_id': commission_account_id,
'ref': 'commission',
'analytic_account_id': commission_analytic_id
}
statement_line_obj.create(cursor, uid,
comm_values,
context=context)
attachment_obj.create(
cursor,
uid,
{
'name': 'statement file',
'datas': file_stream,
'datas_fname': "%s.%s"%(datetime.datetime.now().date(),
ftype),
'res_model': 'account.bank.statement',
'res_id': statement_id,
},
context=context
)
# If user ask to launch completion at end of import, do it !
if prof.launch_import_completion:
self.button_auto_completion(cursor, uid, statement_id, context)
# Write the needed log infos on profile
self.write_logs_after_import(self, cr, uid, prof.id, statement_id,
len(result_row_list), context)
except Exception, exc:
logger.notifyChannel("Statement import",
netsvc.LOG_ERROR,
_("Statement can not be created %s") %(exc,))
statement_obj.unlink(cursor, uid, [statement_id])
raise exc
return statement_id
class AccountStatementLine(Model):
"""Add sparse field on the statement line to allow to store all the
bank infos that are given by an office."""
_inherit = "account.bank.statement.line"
_columns={
'commission_amount': fields.sparse(type='float', string='Line Commission Amount',
serialization_field='additionnal_bank_fields'),
}

View File

@@ -13,35 +13,31 @@
<separator colspan="4" String="Import related infos"/>
<field name="launch_import_completion"/>
<field name="last_import_date"/>
<button icon="gtk-ok" name="launch_import_bank_statement" colspan = "2" string="Import Bank Statement" type="object"/>
<button name="%(account_statement_base_import.statement_importer_action)d"
string="Import Bank Statement"
type="action" icon="gtk-ok"
colspan = "2"/>
<!-- <button icon="gtk-ok" name="launch_import_bank_statement" string="Import Bank Statement" type="object"/> -->
<separator colspan="4" String="Import Logs"/>
<field name="rec_log" colspan="4" nolabel="1"/>
</field>
</field>
</record>
<!-- To port -->
<record id="bank_statement_view_form" model="ir.ui.view">
<field name="name">account_bank_statement_import_base.bank_statement.view_form</field>
<field name="model">account.bank.statement</field>
<field name="inherit_id" ref="account.view_bank_statement_form" />
<field name="type">form</field>
<field name="arch" type="xml">
<data>
<xpath expr="/form/notebook/page/field[@name='line_ids']/form/field[@name='ref']" position="after">
<field name="commission_amount" />
</xpath>
</data>
</field>
</record>
<!-- <record id="action_bank_statement_import" model="ir.actions.act_window">
<field name="name">account bank statement import</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">account.bank.statement.import</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context">{'wizard_object' : 'account.bank.statement.import', 'function' : 'action_import_bank_statement', 'object_link' : 'account.bank.statement.import' }</field>
</record>
<menuitem action="action_bank_statement_import" id="menu_account_bank_import" parent="account.menu_finance"/>
<record id="ir_action_create_scheduler_in_easy_reconcile" model="ir.values">
<field name="key2">client_action_multi</field>
<field name="model">account.bank.statement.import</field>
<field name="name">Create a Scheduler</field>
<field eval="'ir.actions.act_window,%d'%ref('base_scheduler_creator.action_scheduler_creator_wizard')" name="value"/>
<field eval="True" name="object"/>
</record> -->
</data>
</openerp>

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author Nicolas Bessi, Joel Grand-Guillaume. Copyright Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import import_statement

View File

@@ -0,0 +1,128 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Nicolas Bessi, Joel Grand-Guillaume
# Copyright 2011-2012 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
"""
Wizard to import financial institute date in bank statement
"""
from osv import fields, osv
from tools.translate import _
import os
class CreditPartnerStatementImporter(osv.osv_memory):
"""Import Credit statement"""
_name = "credit.statement.import"
def _get_profile(self, cr, uid, context=None):
if context is None: context = {}
res = False
if (context.get('active_model', False) == 'account.statement.profil' and
context.get('active_ids', False)):
res = context['active_ids']
if len(res) > 1:
raise Exception (_('You cannot use this on more than one profile !'))
return res[0]
_columns = {
'profile_id': fields.many2one('account.statement.profil',
'Import configuration parameter',
required=True),
'input_statement': fields.binary('Statement file', required=True),
'partner_id': fields.many2one('res.partner',
'Credit insitute partner',
),
'journal_id': fields.many2one('account.journal',
'Financial journal to use transaction',
),
'input_statement': fields.binary('Statement file', required=True),
'file_name': fields.char('File Name', size=128),
'commission_account_id': fields.many2one('account.account',
'Commission account',
),
'commission_analytic_id': fields.many2one('account.analytic.account',
'Commission analytic account',
),
'receivable_account_id': fields.many2one('account.account',
'Force Receivable/Payable Account'),
'force_partner_on_bank': fields.boolean('Force partner on bank move',
help="Tic that box if you want to use the credit insitute partner\
in the counterpart of the treasury/banking move."
),
'balance_check': fields.boolean('Balance check',
help="Tic that box if you want OpenERP to control the start/end balance\
before confirming a bank statement. If don't ticked, no balance control will be done."
),
}
_defaults = {
'profile_id': _get_profile,
}
def onchange_profile_id(self, cr, uid, ids, profile_id, context=None):
res={}
if profile_id:
c = self.pool.get("account.statement.profil").browse(cr,uid,profile_id)
res = {'value': {'partner_id': c.partner_id and c.partner_id.id or False,
'journal_id': c.journal_id and c.journal_id.id or False, 'commission_account_id': \
c.commission_account_id and c.commission_account_id.id or False,
'receivable_account_id': c.receivable_account_id and c.receivable_account_id.id or False,
'commission_a':c.commission_analytic_id and c.commission_analytic_id.id or False,
'force_partner_on_bank':c.force_partner_on_bank,
'balance_check':c.balance_check,}}
return res
def _check_extension(self, filename):
(shortname, ftype) = os.path.splitext(file_name)
if not ftype:
#We do not use osv exception we do not want to have it logged
raise Exception(_('Please use a file with an extention'))
return ftype
def import_statement(self, cursor, uid, req_id, context=None):
"""This Function import credit card agency statement"""
context = context or {}
if isinstance(req_id, list):
req_id = req_id[0]
importer = self.browse(cursor, uid, req_id, context)
ftype = self._check_extension(importer.file_name)
sid = self.pool.get(
'account.bank.statement').statement_import(
cursor,
uid,
False,
importer.profile_id.id,
importer.input_statement,
ftype.replace('.',''),
context=context
)
# obj_data = self.pool.get('ir.model.data')
# act_obj = self.pool.get('ir.actions.act_window')
# result = obj_data.get_object_reference(cursor, uid, 'account_statement_import', 'action_treasury_statement_tree')
#
# id = result and result[1] or False
# result = act_obj.read(cursor, uid, [id], context=context)[0]
# result['domain'] = str([('id','in',[sid])])
# We should return here the profile for which we executed the import
return {'type': 'ir.actions.act_window_close'}

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="statement_importer_view" model="ir.ui.view">
<field name="name">credit.statement.import.config.view</field>
<field name="model">credit.statement.import</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Import statement">
<group colspan="4" >
<field name="profile_id" />
<field name="input_statement" filename="file_name" colspan="2"/>
<field name="file_name" colspan="2" invisible="1"/>
<separator string="Import Parameters Summary" colspan="4"/>
<field name="partner_id" readonly="1"/>
<field name="journal_id" readonly="1"/>
<field name="commission_account_id" readonly="1"/>
<field name="commission_analytic_id" readonly="1"/>
<field name="receivable_account_id" readonly="1"/>
<field name="force_partner_on_bank" readonly="1"/>
<field name="balance_check" readonly="1"/>
</group>
<separator string="" colspan="4"/>
<group colspan="4" col="6">
<button icon="gtk-cancel" special="cancel" string="Cancel"/>
<button icon="gtk-ok" name="import_statement" string="Import statement" type="object"/>
</group>
</form>
</field>
</record>
<record id="statement_importer_action" model="ir.actions.act_window">
<field name="name">Import statement</field>
<field name="res_model">credit.statement.import</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="statement_importer_view"/>
<field name="target">new</field>
</record>
<menuitem id="statement_importer_menu" name="Import Treasury Statement" action="statement_importer_action" parent="account.menu_finance_periodical_processing"/>
</data>
</openerp>

View File

@@ -19,8 +19,6 @@
#
##############################################################################
import file_parser
import wizard
import statement
import report
import account

View File

@@ -25,7 +25,7 @@
'maintainer': 'Camptocamp',
'category': 'Finance',
'complexity': 'normal', #easy, normal, expert
'depends': ['base_transaction_id'],
'depends': ['account'],
'description': """
The goal of this module is to improve the basic bank statement, help dealing with huge volume of
reconciliation through payment offices like Paypal, Lazer, Visa, Amazon and so on.

View File

@@ -22,7 +22,7 @@ import netsvc
logger = netsvc.Logger()
from openerp.osv.orm import Model, fields
class account_move(osv.osv):
class account_move(Model):
_inherit='account.move'
def unlink(self, cr, uid, ids, context=None):
@@ -33,6 +33,6 @@ class account_move(osv.osv):
if move_line.reconcile_id:
move_line.reconcile_id.unlink(context=context)
return super(account_move, self).unlink(cr, uid, ids, context=context)
account_move()

View File

@@ -20,7 +20,6 @@
##############################################################################
from tools.translate import _
from account_statement_ext.file_parser.parser import FileParser
import datetime
import netsvc
logger = netsvc.Logger()
@@ -395,43 +394,38 @@ class AccountBankSatement(Model):
)
return account_id
def get_default_pay_receiv_accounts(self, cursor, uid, receivable_account_id, context=None):
def get_default_pay_receiv_accounts(self, cursor, uid, context=None):
"""We try to determine default payable/receivable accounts to be used as counterpart
of the journal one.
If receivable_account_id is set (from the profil), take it as receivable/payable
account. Otherwise take it from the property of the partner."""
from the company default propoerty.
"""
account_receivable = False
account_payable = False
if receivable_account_id:
account_receivable = account_payable = receivable_account_id
else:
context = context or {}
property_obj = self.pool.get('ir.property')
model_fields_obj = self.pool.get('ir.model.fields')
model_fields_ids = model_fields_obj.search(
cursor,
uid,
[('name', 'in', ['property_account_receivable',
'property_account_payable']),
('model', '=', 'res.partner'),],
context=context
)
property_ids = property_obj.search(
cursor,
uid, [
('fields_id', 'in', model_fields_ids),
('res_id', '=', False),
],
context=context
)
context = context or {}
property_obj = self.pool.get('ir.property')
model_fields_obj = self.pool.get('ir.model.fields')
model_fields_ids = model_fields_obj.search(
cursor,
uid,
[('name', 'in', ['property_account_receivable',
'property_account_payable']),
('model', '=', 'res.partner'),],
context=context
)
property_ids = property_obj.search(
cursor,
uid, [
('fields_id', 'in', model_fields_ids),
('res_id', '=', False),
],
context=context
)
for erp_property in property_obj.browse(cursor, uid,
property_ids, context=context):
if erp_property.fields_id.name == 'property_account_receivable':
account_receivable = erp_property.value_reference.id
elif erp_property.fields_id.name == 'property_account_payable':
account_payable = erp_property.value_reference.id
for erp_property in property_obj.browse(cursor, uid,
property_ids, context=context):
if erp_property.fields_id.name == 'property_account_receivable':
account_receivable = erp_property.value_reference.id
elif erp_property.fields_id.name == 'property_account_payable':
account_payable = erp_property.value_reference.id
return account_receivable, account_payable
def balance_check(self, cr, uid, st_id, journal_type='bank', context=None):
@@ -465,19 +459,78 @@ class AccountBankSatementLine(Model):
return periods and periods[0] or False
_columns = {
# Set them as required
'ref': fields.char('Reference', size=32, required=True),
# Set them as required + 64 char instead of 32
'ref': fields.char('Reference', size=64, required=True),
'period_id': fields.many2one('account.period', 'Period', required=True),
}
_defaults = {
'period_id': _get_period,
}
def get_values_for_line(self, cr, uid, profile_id = False, partner_id = False, line_type = False, amount = False, context = None):
"""Return the account_id to be used in the line of a bank statement. It'll base the result as follow:
- If a receivable_account_id is set in the profil, return this value and type = general
- Elif line_type is given, take the partner receivable/payable property (payable if type= supplier, receivable
otherwise)
- Elif amount is given, take the partner receivable/payable property (receivable if amount >= 0.0,
payable otherwise). In that case, we also fullfill the type (receivable = customer, payable = supplier)
so it is easier for the accountant to know why the receivable/payable has been chosen
- Then, if no partner are given we look and take the property from the company so we always give a value
for account_id. Note that in that case, we return the receivable one.
:params: profile_id: int/long
:params: line_type: String value like 'general', 'supplier', 'customer'
:params: amount: float
:return A dict of value that can be passed directly to the write method of
the statement line:
{'partner_id': value,
'account_id' : value,
'type' : value,
...
}
"""
if context is None:
context = {}
res = {}
obj_partner = self.pool.get('res.partner')
obj_stat = self.pool.get('account.bank.statement')
receiv_account, pay_account, account_id = False
# If profil has a receivable_account_id, we return it in any case
if profile_id:
profile = self.pool.get("account.statement.profil").browse(cr,uid,profile_id)
if profile.receivable_account_id:
res['account_id'] = profile.receivable_account_id.id
res['type'] = 'general'
return res
# If partner -> take from him
if partner_id:
part = obj_partner.browse(cr, uid, partner_id, context=context)
pay_account = part.property_account_payable.id
receiv_account = part.property_account_receivable.id
# If no value, look on the default company property
if not pay_account or not receiv_account:
receiv_account, pay_account = obj_stat.get_default_pay_receiv_accounts(cr, uid, context=None)
# Now we have both pay and receive account, choose the one to use
# based on line_type first, then amount, otherwise take receivable one.
if line_type is not False:
if line_type == 'supplier':
res['account_id'] = pay_account
else:
res['account_id'] = receiv_account
elif amount is not False:
if amount >= 0:
res['account_id'] = receiv_account
res['type'] = 'customer'
else:
res['account_id'] = pay_account
res['type'] = 'supplier'
if not account_id:
res['account_id'] = receiv_account
return res
def onchange_partner_id(self, cr, uid, ids, partner_id, profile_id, context=None):
"""When changing the partner, we'll now need to look in the profil to determine which
account to use."""
"""Override of the basic method as we need to pass the profile_id in the on_change_type
call."""
obj_partner = self.pool.get('res.partner')
if context is None:
context = {}
@@ -493,26 +546,21 @@ class AccountBankSatementLine(Model):
type = 'supplier'
if part.customer == True:
type = 'customer'
res_type = self.onchange_type(cr, uid, ids, partner_id, type, profile_id, context=context)
res_type = self.onchange_type(cr, uid, ids, partner_id, type, profile_id, context=context) # Chg
if res_type['value'] and res_type['value'].get('account_id', False):
res = {'value': {'type': type, 'account_id': res_type['value']['account_id']}}
else:
res = {'value': {'type': type}}
return {'value': {'type': type, 'account_id': res_type['value']['account_id']}}
return {'value': {'type': type}}
c = self.pool.get("account.statement.profil").browse(cr,uid,profile_id)
acc_id=c.receivable_account_id and c.receivable_account_id.id or False
if acc_id:
res['value'].update({'account_id':acc_id})
return res
# TOFIX: don't seems to work as expected
def onchange_type(self, cr, uid, line_id, partner_id, type, profile_id, context=None):
"""Keep the same features as in standard and call super. If an account is returned,
call the method to compute line values."""
if context is None:
context = {}
res = super(AccountBankSatementLine,self).onchange_type(cr, uid, line_id, partner_id, type, context)
c = self.pool.get("account.statement.profil").browse(cr,uid,profile_id)
acc_id=c.receivable_account_id and c.receivable_account_id.id or False
if acc_id:
res['value'].update({'account_id':acc_id})
if 'account_id' in res['value']:
result = self.get_values_for_line(cr, uid, profile_id = profile_id,
partner_id = partner_id, line_type = type, context = context)
if result:
res['value'].update({'account_id':result['account_id']})
return res

View File

@@ -189,31 +189,13 @@
</field>
</record>
<!-- <record id="action_treasury_statement_tree" model="ir.actions.act_window">
<field name="name">Intermediate Statements</field>
<field name="res_model">account.bank.statement</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,graph</field>
<field name="domain">[('journal_id.type', '=', 'bank')]</field>
<field name="context">{'journal_type':'bank'}</field>
<field name="search_view_id" ref="view_treasury_statement_search"/>
<field name="help">A treasury statement is a summary of all financial transactions occurring a credit card or any other type of intermediate financial account.</field>
</record>
<record model="ir.actions.act_window.view" id="action_treasury_statement_tree_bank">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_treasury_statement_tree"/>
<field name="act_window_id" ref="action_treasury_statement_tree"/>
</record>
<record model="ir.actions.act_window.view" id="action_treasury_statement_form_bank">
<field name="sequence" eval="1"/>
<field name="view_mode">form</field>
<field name="view_id" ref="view_treasury_statement_form"/>
<field name="act_window_id" ref="action_treasury_statement_tree"/>
</record>
<act_window id="act_bank_statement_from_profile"
name="Open Statements"
res_model="account.bank.statement"
src_model="account.statement.profil"
context="{'search_default_profile_id': [active_id]}"
view_type="tree"/>
<menuitem string="Treasury Statements" action="action_treasury_statement_tree" id="menu_treasury_statement_tree" parent="account.menu_finance_bank_and_cash" sequence="9"/> -->
</data>
</openerp>

View File

@@ -25,23 +25,28 @@
'maintainer': 'Camptocamp',
'category': 'Finance',
'complexity': 'normal', #easy, normal, expert
'depends': ['base_transaction_id','account_statement_base_completion'],
'depends': ['account_statement_base_completion', 'base_transaction_id'],
'description': """
Add a completion method based on transaction ID providen by the bank/office. This
transaction ID has been recorded on the SO (by a mapping through the e-commerce connector,
or manually). Completion will look in the SO with that transaction ID to match the partner.
or manually). Completion will look in the SO with that transaction ID to match the partner,
then it will complete the bank statement line with him the fullfill as well the reference
with the found SO name to ease the reconciliation.
So this way, the reconciliation always happend on the SO name stored in ref.
""",
'website': 'http://www.camptocamp.com',
'init_xml': [],
'update_xml': [
"statement_view.xml",
"data.xml",
],
'demo_xml': [],
'test': [],
'installable': True,
'images': [],
'auto_install': False,
'auto_install': True,
'license': 'AGPL-3',
'active': False,
}

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="bank_statement_completion_rule_4" model="account.statement.completion.rule">
<field name="name">Match from line reference (based on transaction ID)</field>
<field name="sequence">40</field>
<field name="function_to_call">get_from_transaction_id_and_so</field>
</field>
</record>
</data>
</openerp>

View File

@@ -24,15 +24,51 @@ import datetime
import netsvc
logger = netsvc.Logger()
from openerp.osv.orm import Model, fields
from openerp.addons.account_statement_base_completion.statement import ErrorTooManyPartner
class AccountStatementCompletionRule(Model):
"""Add a rule based on transaction ID"""
class AccountStatementProfil(Model):
_inherit = "account.statement.profil"
_inherit = "account.statement.completion.rule"
def _get_functions(self):
res = super (self,AccountStatementCompletionRule)._get_functions()
res.append(('get_from_transaction_id_and_so', 'From line reference (based on SO transaction ID'))
return res
class AccountBankSatement(Model):
_columns={
'function_to_call': fields.selection(_get_functions, 'Method'),
}
#TODO : Ensure we match only one partner => Otherwise raise an error !!!
def get_from_transaction_id_and_so(self, cr, uid, line_id, context=None):
"""Match the partner based on the transaction ID field of the SO.
Then, call the generic st_line method to complete other values.
In that case, we always fullfill the reference of the line with the SO name.
Return:
A dict of value that can be passed directly to the write method of
the statement line.
{'partner_id': value,
'account_id' : value,
...}
"""
st_obj = self.pool.get('account.bank.statement.line')
st_line = st_obj.browse(cr,uid,line_id)
res = {}
if st_line:
so_obj = self.pool.get('sale.order')
so_id = so_obj.search(cursor, uid, [('transaction_id', '=', st_line.transaction_id)])
if so_id and len(so_id) == 1:
so = so_obj.browse(cursor, uid, so_id[0])
res['partner_id'] = so.partner_id.id
res['ref'] = so.name
elif so_id and len(so_id) > 1:
raise Exception(_('Line named "%s" was matched by more than one partner.')%(st_line.name,st_line.id))
st_vals = st_obj.get_values_for_line(cr, uid, profile_id = st_line.statement_id.profile_id.id,
partner_id = res.get('partner_id',False), line_type = st_line.type, st_line.amount, context)
res.update(st_vals)
return res
_inherit = "account.bank.statement"
class AccountStatementLine(Model):
_inherit = "account.bank.statement.line"