[FIX] Bug fixing and first running version

(lp:c2c-financial-addons/6.1 rev 24.1.19)
This commit is contained in:
Joël Grand-Guillaume
2012-06-15 16:04:08 +02:00
parent 57215e0aa1
commit 5ddd981875
13 changed files with 312 additions and 168 deletions

View File

@@ -29,9 +29,18 @@
'description': """ 'description': """
The goal of this module is to improve the basic bank statement, help dealing with huge volume of 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. 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. Each bank statement profile can have his own rules to apply respecting a sequence order.
Some basic rules are provided in this module:
1) Match from statement line label (based on partner field 'Bank Statement Label')
2) Match from statement line label (based on partner name)
3) Match from statement line reference (based on SO number)
It add as well a label on the bank statement line (on which the pre-define rules can match) and
a char field on the partner called 'Bank Statement Label'. Using the pre-define rules, you'll be
able to match various label for a partner.
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 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, 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. or anything that will be found in the invoice entry part to make the match.
@@ -41,6 +50,7 @@
'init_xml': [], 'init_xml': [],
'update_xml': [ 'update_xml': [
'statement_view.xml', 'statement_view.xml',
'partner_view.xml',
'data.xml', 'data.xml',
], ],
'demo_xml': [], 'demo_xml': [],

View File

@@ -6,16 +6,25 @@
<field name="name">Match from line label (based on partner field 'Bank Statement Label')</field> <field name="name">Match from line label (based on partner field 'Bank Statement Label')</field>
<field name="sequence">60</field> <field name="sequence">60</field>
<field name="function_to_call">get_from_label_and_partner_field</field> <field name="function_to_call">get_from_label_and_partner_field</field>
</field>
</record> </record>
<record id="bank_statement_completion_rule_3" model="account.statement.completion.rule"> <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="name">Match from line label (based on partner name)</field>
<field name="sequence">70</field> <field name="sequence">70</field>
<field name="function_to_call">get_from_label_and_partner_name</field> <field name="function_to_call">get_from_label_and_partner_name</field>
</field>
</record> </record>
<record id="bank_statement_completion_rule_1" model="account.statement.completion.rule">
<field name="name">Match from line reference (based on SO number)</field>
<field name="sequence">50</field>
<field name="function_to_call">get_from_ref_and_so</field>
</record>
<record id="bank_statement_completion_rule_4" model="account.statement.completion.rule">
<field name="name">Match from line reference (based on Invoice number)</field>
<field name="sequence">40</field>
<field name="function_to_call">get_from_ref_and_invoice</field>
</record>

View File

@@ -22,7 +22,8 @@ from tools.translate import _
import netsvc import netsvc
logger = netsvc.Logger() logger = netsvc.Logger()
from openerp.osv.orm import Model, fields from openerp.osv.orm import Model, fields
from openerp.osv import fields, osv
from operator import itemgetter, attrgetter
class ErrorTooManyPartner(Exception): class ErrorTooManyPartner(Exception):
def __init__(self, value): def __init__(self, value):
@@ -41,12 +42,11 @@ class AccountStatementProfil(Model):
'rule_ids':fields.many2many('account.statement.completion.rule', 'rule_ids':fields.many2many('account.statement.completion.rule',
string='Related statement profiles', string='Related statement profiles',
rel='account_statement_rule_statement_profile_to_rel', rel='as_rul_st_prof_rel',
ids1='profile_id',ids2='rule_id',
), ),
} }
def find_values_from_rules(self, cr, uid, ids, line_id, context=None): def find_values_from_rules(self, cr, uid, id, line_id, context=None):
"""This method will execute all rules, in their sequence order, """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.
@@ -58,15 +58,19 @@ class AccountStatementProfil(Model):
... ...
} }
""" """
if not context: if not context:
context={} context={}
res = {} res = {}
for profile in self.browse(cr, uid, ids, context=context): rule_obj = self.pool.get('account.statement.completion.rule')
for rule in profile.rule_ids: profile = self.browse(cr, uid, id, context=context)
method_to_call = getattr(rule, rule.function_to_call) # We need to respect the sequence order
sorted_array = sorted(profile.rule_ids, key=attrgetter('sequence'))
for rule in sorted_array:
method_to_call = getattr(rule_obj, rule.function_to_call)
result = method_to_call(cr,uid,line_id,context) result = method_to_call(cr,uid,line_id,context)
if result: if result:
return res return result
return res return res
@@ -74,32 +78,89 @@ class AccountStatementCompletionRule(Model):
"""This will represent all the completion method that we can have to """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 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 The goal of a rule is to fullfill at least the partner of the line, but
if possible also the reference because we'll use it in the reconciliation 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 process. The reference should contain the invoice number or the SO number
or any reference that will be matched by the invoice genertaed move.
""" """
_name = "account.statement.completion.rule" _name = "account.statement.completion.rule"
_order = "sequence asc" _order = "sequence asc"
def _get_functions(self): def _get_functions(self, cr, uid, context=None):
"""List of available methods for rules. Override this to add you own.""" """List of available methods for rules. Override this to add you own."""
return [ return [
('get_from_ref_and_invoice', 'From line reference (based on invoice number)'),
('get_from_ref_and_so', 'From line reference (based on SO number)'),
('get_from_label_and_partner_field', 'From line label (based on partner field)'), ('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)'), ('get_from_label_and_partner_name', 'From line label (based on partner name)'),
] ]
_columns={ _columns={
'sequence': fields.integer('Sequence', help="Lower means paresed first."), 'sequence': fields.integer('Sequence', help="Lower means paresed first."),
'name': fields.char('Name'), 'name': fields.char('Name', size=128),
'profile_ids': fields.many2many('account.statement.profil', 'profile_ids': fields.many2many('account.statement.profil',
rel='account_statement_rule_statement_profile_to_rel', rel='as_rul_st_prof_rel',
ids1='rule_id', ids2='profile_id',
string='Related statement profiles'), string='Related statement profiles'),
'function_to_call': fields.selection(_get_functions, 'Method'), 'function_to_call': fields.selection(_get_functions, 'Method'),
} }
def get_from_label_and_partner_field(self, cr, uid, line_id, context=None): def get_from_ref_and_invoice(self, cursor, uid, line_id, context=None):
"""Match the partner based on the invoice number and the reference of the statement
line. 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,
...}
"""
st_obj = self.pool.get('account.bank.statement.line')
st_line = st_obj.browse(cursor,uid,line_id)
res = {}
if st_line:
inv_obj = self.pool.get('account.invoice')
inv_id = inv_obj.search(cursor, uid, [('number', '=', st_line.ref)])
if inv_id and len(inv_id) == 1:
inv = inv_obj.browse(cursor, uid, inv_id[0])
res['partner_id'] = inv.partner_id.id
elif inv_id and len(inv_id) > 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(cursor, 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_ref_and_so(self, cursor, uid, line_id, context=None):
"""Match the partner based on the SO number and the reference of the statement
line. 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,
...}
"""
st_obj = self.pool.get('account.bank.statement.line')
st_line = st_obj.browse(cursor,uid,line_id)
res = {}
if st_line:
so_obj = self.pool.get('sale.order')
so_id = so_obj.search(cursor, uid, [('name', '=', st_line.ref)])
if so_id and len(so_id) == 1:
so = so_obj.browse(cursor, uid, so_id[0])
res['partner_id'] = so.partner_id.id
elif so_id and len(so_id) > 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(cursor, 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_field(self, cursor, uid, line_id, context=None):
"""Match the partner based on the label field of the statement line """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. and the text defined in the 'bank_statement_label' field of the partner.
Remember that we can have values separated with ; Then, call the generic Remember that we can have values separated with ; Then, call the generic
@@ -114,24 +175,24 @@ class AccountStatementCompletionRule(Model):
""" """
partner_obj = self.pool.get('res.partner') partner_obj = self.pool.get('res.partner')
st_obj = self.pool.get('account.bank.statement.line') st_obj = self.pool.get('account.bank.statement.line')
st_line = st_obj.browse(cr,uid,line_id) st_line = st_obj.browse(cursor,uid,line_id)
res = {} res = {}
compt = 0 compt = 0
if st_line: if st_line:
ids = partner_obj.search(cr, uid, [['bank_statement_label', '!=', False]], context=context) ids = partner_obj.search(cursor, uid, [['bank_statement_label', '!=', False]], context=context)
for partner in self.browse(cr, uid, ids, context=context): for partner in self.browse(cursor, uid, ids, context=context):
for partner_label in partner.bank_statement_label.split(';'): for partner_label in partner.bank_statement_label.split(';'):
if partner_label in st_line.label: if partner_label in st_line.label:
compt += 1 compt += 1
res['partner_id'] = partner.id res['partner_id'] = partner.id
if compt > 1: if compt > 1:
raise ErrorTooManyPartner(_('Line named "%s" was matched by more than one partner.')%(st_line.name,st_line.id)) 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, st_vals = st_obj.get_values_for_line(cursor, 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) partner_id = res.get('partner_id',False), line_type = st_line.type, amount = st_line.amount, context = context)
res.update(st_vals) res.update(st_vals)
return res return res
def get_from_label_and_partner_name(self, cr, uid, line_id, context=None): def get_from_label_and_partner_name(self, cursor, uid, line_id, context=None):
"""Match the partner based on the label field of the statement line """Match the partner based on the label field of the statement line
and the name of the partner. and the name of the partner.
Then, call the generic st_line method to complete other values. Then, call the generic st_line method to complete other values.
@@ -145,17 +206,17 @@ class AccountStatementCompletionRule(Model):
""" """
res = {} res = {}
st_obj = self.pool.get('account.bank.statement.line') st_obj = self.pool.get('account.bank.statement.line')
st_line = st_obj.browse(cr,uid,line_id) st_line = st_obj.browse(cursor,uid,line_id)
if st_line: if st_line:
sql = "SELECT id FROM res_partner WHERE name ~* '.*%s.*'" sql = "SELECT id FROM res_partner WHERE name ~* '.*%s.*'"
cr.execute(sql, (st_line.label,)) cursor.execute(sql, (st_line.label,))
result = cr.fetchall() result = cursor.fetchall()
if len(result) > 1: if len(result) > 1:
raise ErrorTooManyPartner(_('Line named "%s" was matched by more than one partner.')%(st_line.name,st_line.id)) raise ErrorTooManyPartner(_('Line named "%s" was matched by more than one partner.')%(st_line.name,st_line.id))
for id in result: for id in result:
res['partner_id'] = id res['partner_id'] = id
st_vals = st_obj.get_values_for_line(cr, uid, profile_id = st_line.statement_id.profile_id.id, st_vals = st_obj.get_values_for_line(cursor, 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) partner_id = res.get('partner_id',False), line_type = st_line.type, amount = st_line.amount, context = context)
res.update(st_vals) res.update(st_vals)
return res return res
@@ -167,79 +228,45 @@ class AccountStatementLine(Model):
_inherit = "account.bank.statement.line" _inherit = "account.bank.statement.line"
_columns={ _columns={
# 'email_address': fields.char('Email', size=64),
# 'order_ref': fields.char('Order Ref', size=64),
# 'partner_name': fields.char('Partner Name', size=64),
#
# Only label for a start, but other module can add their own # Only label for a start, but other module can add their own
'additionnal_bank_fields' : fields.serialized('Additionnal infos from bank', help="Used by completion and import system."), 'additionnal_bank_fields' : fields.serialized('Additionnal infos from bank',
help="Used by completion and import system. Adds every field that is present in your bank/office \
statement file"),
'label': fields.sparse(type='char', string='Label', 'label': fields.sparse(type='char', string='Label',
serialization_field='additionnal_bank_fields'), serialization_field='additionnal_bank_fields'),
'already_completed': fields.boolean("Auto-Completed",
help="When this checkbox is ticked, the auto-completion process/button will ignore it."),
} }
_defaults = {
'already_completed': False,
}
def get_line_values_from_rules(self, cr, uid, ids, 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 We'll try to find out the values related to the line based on what we
have and rules setted on the profile.. have and rules setted on the profile..
We ignore line for which already_completed is ticked!
""" """
profile_obj = self.pool.get('account.statement.profil') profile_obj = self.pool.get('account.statement.profil')
res={} res={}
errors_stack = [] errors_stack = []
for line in self.browse(cr,uid, ids, context): for line in self.browse(cr,uid, ids, context):
if not line.already_completed:
try: try:
vals = profile_obj.find_values_from_rules(cr, uid, ids, line.id, context) vals = profile_obj.find_values_from_rules(cr, uid, line.statement_id.profile_id.id, line.id, context)
res[line.id]=vals res[line.id]=vals
except ErrorTooManyPartner, exc: except ErrorTooManyPartner, exc:
msg = "Line ID %s had following error: %s" % (line.id, str(exc)) msg = "Line ID %s had following error: %s" % (line.id, str(exc))
errors_stack.append(msg) 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: if errors_stack:
msg = u"\n".join(errors_stack) msg = u"\n".join(errors_stack)
raise ErrorTooManyPartner(msg) raise ErrorTooManyPartner(msg)
return res 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 AccountBankSatement(Model): class AccountBankSatement(Model):
""" """
We add a basic button and stuff to support the auto-completion We add a basic button and stuff to support the auto-completion
@@ -248,6 +275,8 @@ class AccountBankSatement(Model):
_inherit = "account.bank.statement" _inherit = "account.bank.statement"
def button_auto_completion(self, cr, uid, ids, context=None): def button_auto_completion(self, cr, uid, ids, context=None):
"""Complete line with values given by rules and tic the already_completed
checkbox so we won't compute them again until the user untick it !"""
if not context: if not context:
context={} context={}
stat_line_obj = self.pool.get('account.bank.statement.line') stat_line_obj = self.pool.get('account.bank.statement.line')
@@ -260,8 +289,9 @@ class AccountBankSatement(Model):
except ErrorTooManyPartner, exc: except ErrorTooManyPartner, exc:
errors_msg = str(exc) errors_msg = str(exc)
for id in line_ids: for id in line_ids:
vals = res[line.id] vals = res.get(id, False)
if vals: if vals:
vals['already_completed'] = True
stat_line_obj.write(cr, uid, id, vals, context=ctx) stat_line_obj.write(cr, uid, id, vals, context=ctx)
# cr.commit() # cr.commit()
# TOTEST: I don't know if this is working... # TOTEST: I don't know if this is working...

View File

@@ -10,11 +10,12 @@
<field name="type">form</field> <field name="type">form</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<data> <data>
<xpath expr="/form/notebook/page/field[@name='line_ids']/tree/field[@name='sequence']" position="before">
<field name="already_completed" />
</xpath>
<xpath expr="/form/notebook/page/field[@name='line_ids']/form/field[@name='ref']" position="after"> <xpath expr="/form/notebook/page/field[@name='line_ids']/form/field[@name='ref']" position="after">
<!-- <field name="partner_name" />
<field name="order_ref" />
<field name="email_address" /> -->
<field name="label" /> <field name="label" />
<field name="already_completed" />
</xpath> </xpath>
<xpath expr="/form/group[2]" position="attributes"> <xpath expr="/form/group[2]" position="attributes">
@@ -29,6 +30,57 @@
</field> </field>
</record> </record>
<record id="statement_rules_view_form" model="ir.ui.view">
<field name="name">account.statement.profil.view</field>
<field name="model">account.statement.profil</field>
<field name="inherit_id" ref="account_statement_ext.statement_importer_view_form"/>
<field name="type">form</field>
<field name="arch" type="xml">
<field name="bank_statement_prefix" position="after">
<separator colspan="4" string="Auto-Completion Rules"/>
<field name="rule_ids" colspan="4" nolabel="1"/>
</field>
</field>
</record>
<record id="statement_st_completion_rule_view_form" model="ir.ui.view">
<field name="name">account.statement.completion.rule.view</field>
<field name="model">account.statement.completion.rule</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Statement Completion Rule">
<field name="sequence"/>
<field name="name" select="1" />
<field name="function_to_call"/>
<separator colspan="4" string="Related Profiles"/>
<field name="profile_ids" nolabel="1" colspan="4"/>
</form>
</field>
</record>
<record id="statement_st_completion_rule_view_tree" model="ir.ui.view">
<field name="name">account.statement.completion.rule.view</field>
<field name="model">account.statement.completion.rule</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Statement Completion Rule">
<field name="sequence"/>
<field name="name" select="1" />
<field name="profile_ids" />
<field name="function_to_call"/>
</tree>
</field>
</record>
<record id="action_st_completion_rule_tree" model="ir.actions.act_window">
<field name="name">Statement Completion Rule</field>
<field name="res_model">account.statement.completion.rule</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem string="Statement Completion Rule" action="action_st_completion_rule_tree" id="menu_action_st_completion_rule_tree_menu" parent="account.menu_configuration_misc" sequence="30"/>
</data> </data>
</openerp> </openerp>

View File

@@ -56,6 +56,8 @@
'website': 'http://www.camptocamp.com', 'website': 'http://www.camptocamp.com',
'init_xml': [], 'init_xml': [],
'update_xml': [ 'update_xml': [
"wizard/import_statement_view.xml",
"statement_view.xml",
], ],
'demo_xml': [], 'demo_xml': [],
'test': [], 'test': [],

View File

@@ -23,7 +23,9 @@ import base64
import csv import csv
import tempfile import tempfile
import datetime import datetime
from . import parser # from . import parser
from parser import BankStatementImportParser
from parser import UnicodeDictReader
try: try:
import xlrd import xlrd
except: except:
@@ -33,7 +35,7 @@ class FileParser(BankStatementImportParser):
"""Abstract clall for that help to build a specific parser for all """Abstract clall for that help to build a specific parser for all
.csv and .xls files""" .csv and .xls files"""
def __init__(self, parse_name=None, keys_to_validate={}, ftype='csv', convertion_dict=None, *args, **kwargs): def __init__(self, parse_name, 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 :param: convertion_dict : keys and type to convert of every column in the file like
{ {
@@ -46,7 +48,7 @@ class FileParser(BankStatementImportParser):
""" """
super(self,FileParser).__init__(parse_name, *args, **kwargs) super(FileParser, self).__init__(parse_name, *args, **kwargs)
if ftype in ('csv', 'xls'): if ftype in ('csv', 'xls'):
self.ftype = ftype self.ftype = ftype
else: else:
@@ -69,7 +71,7 @@ class FileParser(BankStatementImportParser):
def _post(self, *args, **kwargs): def _post(self, *args, **kwargs):
"""Cast row type depending on the file format .csv or .xls""" """Cast row type depending on the file format .csv or .xls"""
self.result_row_list = self._cast_rows(kwargs) self.result_row_list = self._cast_rows(*args, **kwargs)
return True return True
def _parse(self, *args, **kwargs): def _parse(self, *args, **kwargs):
@@ -135,7 +137,7 @@ class FileParser(BankStatementImportParser):
line[rule] = conversion_rules[rule](line[rule]) line[rule] = conversion_rules[rule](line[rule])
return result_set return result_set
def _cast_rows(self): def _cast_rows(self, *args, **kwargs):
func = getattr(self, '_from_%s'%(self.ftype)) func = getattr(self, '_from_%s'%(self.ftype))
res = func(self.result_row_list, self.convertion_dict) res = func(self.result_row_list, self.convertion_dict)
return res return res

View File

@@ -23,7 +23,8 @@ import base64
import csv import csv
import tempfile import tempfile
import datetime import datetime
from . import file_parser # from . import file_parser
from file_parser import FileParser
try: try:
import xlrd import xlrd
except: except:
@@ -36,7 +37,7 @@ class GenericFileParser(FileParser):
def __init__(self, parse_name = None, ftype='csv'): def __init__(self, parse_name, ftype='csv'):
convertion_dict = { convertion_dict = {
'ref': unicode, 'ref': unicode,
'label': unicode, 'label': unicode,
@@ -48,7 +49,7 @@ class GenericFileParser(FileParser):
keys_to_validate = ['ref', 'label', 'date', 'amount', 'commission_amount'] 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 ): super(GenericFileParser,self).__init__(parse_name, keys_to_validate=keys_to_validate, ftype=ftype, convertion_dict=convertion_dict)
@classmethod @classmethod
def parser_for(cls, parser_name): def parser_for(cls, parser_name):

View File

@@ -19,6 +19,7 @@
# #
############################################################################## ##############################################################################
import base64 import base64
import csv
def UnicodeDictReader(utf8_data, **kwargs): def UnicodeDictReader(utf8_data, **kwargs):
@@ -31,7 +32,7 @@ class BankStatementImportParser(object):
format to import in a bank statement""" format to import in a bank statement"""
def __init__(self, parser_name = None, *args, **kwargs): def __init__(self, parser_name, *args, **kwargs):
# The name of the parser as it will be called # The name of the parser as it will be called
self.parser_name = parser_name self.parser_name = parser_name
# The result as a list of row # The result as a list of row
@@ -97,12 +98,12 @@ class BankStatementImportParser(object):
self.filebuffer = filebuffer self.filebuffer = filebuffer
else: else:
raise Exception(_('No buffer file given.')) raise Exception(_('No buffer file given.'))
self._format(args, kwargs) self._format(*args, **kwargs)
self._pre(args, kwargs) self._pre(*args, **kwargs)
self._parse(args, kwargs) self._parse(*args, **kwargs)
self._validate(args, kwargs) self._validate(*args, **kwargs)
self._post(args, kwargs) self._post(*args, **kwargs)
return self.result_row_list, return self.result_row_list
def itersubclasses(cls, _seen=None): def itersubclasses(cls, _seen=None):
""" """

View File

@@ -24,14 +24,17 @@ import datetime
import netsvc import netsvc
logger = netsvc.Logger() logger = netsvc.Logger()
from openerp.osv.orm import Model, fields from openerp.osv.orm import Model, fields
from openerp.osv import fields, osv
# from account_statement_base_import.parser.file_parser import FileParser # from account_statement_base_import.parser.file_parser import FileParser
from parser import new_bank_statement_parser from parser import new_bank_statement_parser
import sys
import traceback
class AccountStatementProfil(Model): class AccountStatementProfil(Model):
_inherit = "account.statement.profil" _inherit = "account.statement.profil"
def get_type_selection(self, cr, uid, context=None): def get_import_type_selection(self, cr, uid, context=None):
""" """
Has to be inherited to add parser Has to be inherited to add parser
""" """
@@ -50,10 +53,12 @@ class AccountStatementProfil(Model):
} }
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):
if type(ids) is int:
ids = [ids]
for id in ids: for id in ids:
log = self.read(cr, uid, id, ['rec_log'], context=context)['rec_log'] log = self.read(cr, uid, id, ['rec_log'], context=context)['rec_log']
log_line = log and log.split("\n") or [] log_line = log and log.split("\n") or []
import_date = datetime.now().strftime('%Y-%m-%d %H:%M:%S') import_date = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
log_line[0:0] = [import_date + ' : ' log_line[0:0] = [import_date + ' : '
+ _("Bank Statement ID %s have been imported with %s lines ") %(statement_id, num_lines)] + _("Bank Statement ID %s have been imported with %s lines ") %(statement_id, num_lines)]
log = "\n".join(log_line) log = "\n".join(log_line)
@@ -62,6 +67,69 @@ class AccountStatementProfil(Model):
"Bank Statement ID %s have been imported with %s lines "%(statement_id, num_lines)) "Bank Statement ID %s have been imported with %s lines "%(statement_id, num_lines))
return True return True
def create_global_commission_line(self, cr, uid, commission_global_amount,
result_row_list, profile, statement_id, context):
"""Create the global commission line if there is one. The global commission is computed by
summing the commission column of each row. Feel free to override the methode to compute
your own commission line from the result_row_list.
:params: cr: The DB Cursor
:params: uid: int/long ID of the current user in the system
:params: commission_global_amount: float
:params: result_row_list: [{'key':value}]
:params: profile: browserecord of account.statement.profile
:params: statement_id : int/long of the current importing statement ID
:params: context: global context
return: an ID of the statement line that represent the global commission
for the statement.
"""
res = False
if commission_global_amount:
partner_id = profile.partner_id and profile.partner_id.id or False
commission_account_id = profile.commission_account_id and profile.commission_account_id.id or False
commission_analytic_id = profile.commission_analytic_id and profile.commission_analytic_id.id or False
statement_line_obj = self.pool.get('account.bank.statement.line')
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,
# !! We set the already_completed so auto-completion will not update those values !
'already_completed': True,
}
res = statement_line_obj.create(cr, uid,
comm_values,
context=context)
return res
def prepare_statetement_lines_vals(self, cursor, uid, parser_line,
account_payable, account_receivable, statement_id, context):
"""Hook to change the values of a line. Overide it to compute or change
the account or any other values (e.g. if a line is the global commission)"""
values = {
'name': parser_line.get('label', parser_line.get('ref','/')),
'date': parser_line.get('date', datetime.datetime.now().date()),
'amount': parser_line['amount'],
'ref': parser_line['ref'],
'type': 'customer',
'statement_id': statement_id,
'label': parser_line.get('label',''),
'commission_amount': parser_line.get('commission_amount', 0.0),
#'account_id': journal.default_debit_account_id
}
values['account_id'] = statement_obj.get_account_for_counterpart(
cursor,
uid,
parser_line['amount'],
account_receivable,
account_payable
)
return values
def statement_import(self, cursor, uid, ids, profile_id, file_stream, ftype="csv", context=None): 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 """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 with the values of the file providen, but will not complete data (like finding the partner, or
@@ -76,69 +144,34 @@ class AccountStatementProfil(Model):
raise osv.except_osv( raise osv.except_osv(
_("No Profile !"), _("No Profile !"),
_("You must provide a valid profile to import a bank statement !")) _("You must provide a valid profile to import a bank statement !"))
else:
prof = prof_obj.browse(cursor,uid,profile_id,context) 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
parser = new_bank_statement_parser(parse_name=prof.import_type, ftype=ftype) parser = new_bank_statement_parser(prof.import_type, ftype=ftype)
result_row_list = parser.parse(file_stream) result_row_list = parser.parse(file_stream)
# Check all key are present in account.bank.statement.line !! # Check all key are present in account.bank.statement.line !!
parsed_cols = self.result_row_list[0].keys() parsed_cols = 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( raise osv.except_osv(
_("Missing column !"), _("Missing column !"),
_("Column %s you try to import is not present in the bank statement line !") %(col)) _("Column %s you try to import is not present in the bank statement line !") %(col))
statement_id = statement_obj.create(cursor,uid,{'profile_id':prof.id,},context) 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) account_receivable, account_payable = statement_obj.get_default_pay_receiv_accounts(cursor, uid, context)
commission_global_amount = 0.0 commission_global_amount = 0.0
try: try:
# Record every line in the bank statement and compute the global commission # Record every line in the bank statement and compute the global commission
# based on the commission_amount column # based on the commission_amount column
for line in result_row_list: for line in result_row_list:
line_partner_id = False
line_to_reconcile = False
commission_global_amount += line.get('commission_amount', 0.0) commission_global_amount += line.get('commission_amount', 0.0)
values = { values = self.prepare_statetement_lines_vals(cursor, uid, line, account_payable,
'name': line.get('label', line.get('ref','/')), account_receivable, statement_id, context)
'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 # we finally create the line in system
statement_line_obj.create(cursor, uid, values, context=context) statement_line_obj.create(cursor, uid, values, context=context)
# we create commission line # we create commission line
if commission_global_amount: self.create_global_commission_line(cursor, uid, commission_global_amount,
comm_values = { result_row_list, prof, statement_id, context)
'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( attachment_obj.create(
cursor, cursor,
@@ -158,16 +191,17 @@ class AccountStatementProfil(Model):
self.button_auto_completion(cursor, uid, statement_id, context) self.button_auto_completion(cursor, uid, statement_id, context)
# Write the needed log infos on profile # Write the needed log infos on profile
self.write_logs_after_import(self, cr, uid, prof.id, statement_id, self.write_logs_after_import(cursor, uid, prof.id, statement_id,
len(result_row_list), context) len(result_row_list), context)
except Exception, exc: except Exception, exc:
logger.notifyChannel("Statement import",
netsvc.LOG_ERROR,
_("Statement can not be created %s") %(exc,))
statement_obj.unlink(cursor, uid, [statement_id]) statement_obj.unlink(cursor, uid, [statement_id])
raise exc error_type, error_value, trbk = sys.exc_info()
st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value)
st += ''.join(traceback.format_tb(trbk, 30))
raise osv.except_osv(
_("Statement import error"),
_("The statement cannot be created : %s") %(st))
return statement_id return statement_id

View File

@@ -10,22 +10,22 @@
<field name="type">form</field> <field name="type">form</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="bank_statement_prefix" position="after"> <field name="bank_statement_prefix" position="after">
<separator colspan="4" String="Import related infos"/> <separator colspan="4" string="Import related infos"/>
<field name="launch_import_completion"/> <field name="launch_import_completion"/>
<field name="last_import_date"/> <field name="last_import_date"/>
<field name="import_type"/>
<button name="%(account_statement_base_import.statement_importer_action)d" <button name="%(account_statement_base_import.statement_importer_action)d"
string="Import Bank Statement" string="Import Bank Statement"
type="action" icon="gtk-ok" type="action" icon="gtk-ok"
colspan = "2"/> colspan = "2"/>
<!-- <button icon="gtk-ok" name="launch_import_bank_statement" string="Import Bank Statement" type="object"/> --> <separator colspan="4" string="Import Logs"/>
<separator colspan="4" String="Import Logs"/>
<field name="rec_log" colspan="4" nolabel="1"/> <field name="rec_log" colspan="4" nolabel="1"/>
</field> </field>
</field> </field>
</record> </record>
<record id="bank_statement_view_form" model="ir.ui.view"> <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="name">account_bank_statement.bank_statement.view_form</field>
<field name="model">account.bank.statement</field> <field name="model">account.bank.statement</field>
<field name="inherit_id" ref="account.view_bank_statement_form" /> <field name="inherit_id" ref="account.view_bank_statement_form" />
<field name="type">form</field> <field name="type">form</field>

View File

@@ -41,6 +41,7 @@ class CreditPartnerStatementImporter(osv.osv_memory):
if len(res) > 1: if len(res) > 1:
raise Exception (_('You cannot use this on more than one profile !')) raise Exception (_('You cannot use this on more than one profile !'))
return res[0] return res[0]
return res
_columns = { _columns = {
@@ -93,7 +94,7 @@ class CreditPartnerStatementImporter(osv.osv_memory):
return res return res
def _check_extension(self, filename): def _check_extension(self, filename):
(shortname, ftype) = os.path.splitext(file_name) (shortname, ftype) = os.path.splitext(filename)
if not ftype: if not ftype:
#We do not use osv exception we do not want to have it logged #We do not use osv exception we do not want to have it logged
raise Exception(_('Please use a file with an extention')) raise Exception(_('Please use a file with an extention'))
@@ -107,7 +108,7 @@ class CreditPartnerStatementImporter(osv.osv_memory):
importer = self.browse(cursor, uid, req_id, context) importer = self.browse(cursor, uid, req_id, context)
ftype = self._check_extension(importer.file_name) ftype = self._check_extension(importer.file_name)
sid = self.pool.get( sid = self.pool.get(
'account.bank.statement').statement_import( 'account.statement.profil').statement_import(
cursor, cursor,
uid, uid,
False, False,

View File

@@ -24,6 +24,7 @@ import datetime
import netsvc import netsvc
logger = netsvc.Logger() logger = netsvc.Logger()
from openerp.osv.orm import Model, fields from openerp.osv.orm import Model, fields
from openerp.osv import fields, osv
class AccountStatementProfil(Model): class AccountStatementProfil(Model):
"""A Profile will contain all infos related to the type of """A Profile will contain all infos related to the type of
@@ -493,7 +494,7 @@ class AccountBankSatementLine(Model):
res = {} res = {}
obj_partner = self.pool.get('res.partner') obj_partner = self.pool.get('res.partner')
obj_stat = self.pool.get('account.bank.statement') obj_stat = self.pool.get('account.bank.statement')
receiv_account, pay_account, account_id = False receiv_account = pay_account = account_id = False
# If profil has a receivable_account_id, we return it in any case # If profil has a receivable_account_id, we return it in any case
if profile_id: if profile_id:
profile = self.pool.get("account.statement.profil").browse(cr,uid,profile_id) profile = self.pool.get("account.statement.profil").browse(cr,uid,profile_id)

View File

@@ -189,12 +189,13 @@
</field> </field>
</record> </record>
<act_window id="act_bank_statement_from_profile" <act_window id="act_bank_statement_from_profile"
name="Open Statements" name="Open Statements"
res_model="account.bank.statement" res_model="account.bank.statement"
src_model="account.statement.profil" src_model="account.statement.profil"
context="{'search_default_profile_id': [active_id]}" domain="[('profile_id','=',active_id),]"
view_type="tree"/> view_type="form"/>
</data> </data>