[FIX] Various fixes after presentation to F.Clementi

(lp:c2c-financial-addons/6.1 rev 24.1.23)
This commit is contained in:
Joël Grand-Guillaume
2012-06-18 15:55:15 +02:00
parent 5f56e5d4e3
commit 0e70448fac
19 changed files with 366 additions and 101 deletions

View File

@@ -36,6 +36,7 @@
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)
3) Match from statement line reference (based on Invoice 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

View File

@@ -58,7 +58,6 @@ class AccountStatementProfil(Model):
...
}
"""
if not context:
context={}
res = {}
@@ -111,7 +110,7 @@ class AccountStatementCompletionRule(Model):
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.
the statement line or {}
{'partner_id': value,
'account_id' : value,
...}
@@ -122,6 +121,7 @@ class AccountStatementCompletionRule(Model):
if st_line:
inv_obj = self.pool.get('account.invoice')
inv_id = inv_obj.search(cursor, uid, [('number', '=', st_line.ref)])
if inv_id:
if inv_id and len(inv_id) == 1:
inv = inv_obj.browse(cursor, uid, inv_id[0])
res['partner_id'] = inv.partner_id.id
@@ -138,7 +138,7 @@ class AccountStatementCompletionRule(Model):
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.
the statement line or {}
{'partner_id': value,
'account_id' : value,
...}
@@ -149,6 +149,7 @@ class AccountStatementCompletionRule(Model):
if st_line:
so_obj = self.pool.get('sale.order')
so_id = so_obj.search(cursor, uid, [('name', '=', st_line.ref)])
if so_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
@@ -168,7 +169,7 @@ class AccountStatementCompletionRule(Model):
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.
the statement line or {}
{'partner_id': value,
'account_id' : value,
...}
@@ -187,6 +188,7 @@ class AccountStatementCompletionRule(Model):
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))
if res:
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)
@@ -198,7 +200,7 @@ class AccountStatementCompletionRule(Model):
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.
the statement line or {}
{'partner_id': value,
'account_id' : value,
@@ -215,6 +217,7 @@ class AccountStatementCompletionRule(Model):
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
if res:
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)
@@ -252,13 +255,19 @@ class AccountStatementLine(Model):
We ignore line for which already_completed is ticked!
"""
profile_obj = self.pool.get('account.statement.profil')
st_obj = self.pool.get('account.bank.statement.line')
res={}
errors_stack = []
for line in self.browse(cr,uid, ids, context):
if not line.already_completed:
try:
# Take the default values
res[line.id] = st_obj.get_values_for_line(cr, uid, profile_id = line.statement_id.profile_id.id,
line_type = line.type, amount = line.amount, context = context)
# Ask the rule
vals = profile_obj.find_values_from_rules(cr, uid, line.statement_id.profile_id.id, line.id, context)
res[line.id]=vals
# Merge the result
res[line.id].update(vals)
except ErrorTooManyPartner, exc:
msg = "Line ID %s had following error: %s" % (line.id, str(exc))
errors_stack.append(msg)

View File

@@ -13,7 +13,8 @@
<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='sequence']" position="after">
<separator colspan="4" string="Importation related infos"/>
<field name="label" />
<field name="already_completed" />
</xpath>
@@ -23,7 +24,7 @@
</xpath>
<xpath expr="/form/group/field[@name='balance_end']" position="after">
<button name="button_auto_completion" string="Auto Completion" type="object" colspan="1"/>
<button name="button_auto_completion" string="Auto Completion" states='draft,open' type="object" colspan="1"/>
</xpath>
</data>

View File

@@ -33,7 +33,7 @@ except:
class FileParser(BankStatementImportParser):
"""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, keys_to_validate={}, ftype='csv', convertion_dict=None, *args, **kwargs):
"""

View File

@@ -55,7 +55,42 @@ class GenericFileParser(FileParser):
def parser_for(cls, parser_name):
return parser_name == 'generic_csvxls_so'
def get_st_line_vals(self, line, *args, **kwargs):
"""This method must return a dict of vals that can be passed to create
method of statement line in order to record it. It is the responsibility
of every parser to give this dict of vals, so each one can implement his
own way of recording the lines.
:param: line: a dict of vals that represent a line of result_row_list
:return: dict of values to give to the create method of statement line,
it MUST contain at least:
{
'name':value,
'date':value,
'amount':value,
'ref':value,
'label':value,
'commission_amount':value,
}
In this generic parser, the commission is given for every line, so we store it
for each one.
"""
return {
'name': line.get('label', line.get('ref','/')),
'date': line.get('date', datetime.datetime.now().date()),
'amount': line.get('amount', 0.0),
'ref': line.get('ref','/'),
'label': line.get('label',''),
'commission_amount': line.get('commission_amount', 0.0),
}
def _post(self, *args, **kwargs):
"""Compute the commission from value of each line"""
res = super(GenericFileParser, self)._post(*args, **kwargs)
val = 0.0
for row in self.result_row_list:
val += row.get('commission_amount',0.0)
self.commission_global_amount = val
return res

View File

@@ -35,7 +35,8 @@ class BankStatementImportParser(object):
def __init__(self, parser_name, *args, **kwargs):
# The name of the parser as it will be called
self.parser_name = parser_name
# The result as a list of row
# The result as a list of row. One row per line of data in the file, but
# not the commission one !
self.result_row_list = None
# The file buffer on which to work on
self.filebuffer = None
@@ -84,6 +85,32 @@ class BankStatementImportParser(object):
amount in the self.commission_global_amount one."""
return NotImplementedError
def get_st_line_vals(self, line, *args, **kwargs):
"""This method must return a dict of vals that can be passed to create
method of statement line in order to record it. It is the responsibility
of every parser to give this dict of vals, so each one can implement his
own way of recording the lines.
:param: line: a dict of vals that represent a line of result_row_list
:return: dict of values to give to the create method of statement line,
it MUST contain at least:
{
'name':value,
'date':value,
'amount':value,
'ref':value,
}
"""
return NotImplementedError
def get_st_line_commision(self, *args, **kwargs):
"""This is called by the importation method to create the commission line in
the bank statement. We will always create one line for the commission in the
bank statement, but it could be computated from a value of each line, or given
in a single line for the whole file.
return: float of the whole commission
"""
return self.commission_global_amount
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

View File

@@ -67,23 +67,22 @@ class AccountStatementProfil(Model):
"Bank Statement ID %s have been imported with %s lines "%(statement_id, num_lines))
return True
def create_global_commission_line(self, cr, uid, commission_global_amount,
def prepare_global_commission_line_vals(self, cr, uid, parser,
result_row_list, profile, statement_id, context):
"""Create the global commission line if there is one. The global commission is computed by
"""Prepare 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.
:param: cr: The DB Cursor
:param: uid: int/long ID of the current user in the system
:param: browse_record of the current parser
:param: result_row_list: [{'key':value}]
:param: profile: browserecord of account.statement.profile
:param: statement_id : int/long of the current importing statement ID
:param: context: global context
return: dict of vals that will be passed to create method of statement line.
"""
res = False
if commission_global_amount:
comm_values = False
if parser.get_st_line_commision():
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
@@ -91,7 +90,7 @@ class AccountStatementProfil(Model):
comm_values = {
'name': 'IN '+ _('Commission line'),
'date': datetime.datetime.now().date(),
'amount': commission_global_amount,
'amount': parser.get_st_line_commision(),
'partner_id': partner_id,
'type': 'general',
'statement_id': statement_id,
@@ -101,30 +100,20 @@ class AccountStatementProfil(Model):
# !! 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
return comm_values
def prepare_statetement_lines_vals(self, cursor, uid, parser_line,
def prepare_statetement_lines_vals(self, cursor, uid, parser_vals,
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
}
"""Hook to build the values of a line from the parser returned values. At
least it fullfill the statement_id and account_id. Overide it to add your
own completion if needed. """
statement_obj = self.pool.get('account.bank.statement')
values = parser_vals
values['statement_id']= statement_id
values['account_id'] = statement_obj.get_account_for_counterpart(
cursor,
uid,
parser_line['amount'],
parser_vals['amount'],
account_receivable,
account_payable
)
@@ -134,6 +123,7 @@ class AccountStatementProfil(Model):
"""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.
It will also create the commission line if it apply.
"""
context = context or {}
statement_obj = self.pool.get('account.bank.statement')
@@ -158,20 +148,19 @@ class AccountStatementProfil(Model):
statement_id = statement_obj.create(cursor,uid,{'profile_id':prof.id,},context)
account_receivable, account_payable = statement_obj.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:
commission_global_amount += line.get('commission_amount', 0.0)
values = self.prepare_statetement_lines_vals(cursor, uid, line, account_payable,
parser_vals = parser.get_st_line_vals(line)
values = self.prepare_statetement_lines_vals(cursor, uid, parser_vals, account_payable,
account_receivable, statement_id, context)
# we finally create the line in system
statement_line_obj.create(cursor, uid, values, context=context)
# we create commission line
self.create_global_commission_line(cursor, uid, commission_global_amount,
result_row_list, prof, statement_id, context)
# Build and create the global commission line for the whole statement
comm_vals = self.prepare_global_commission_line_vals(cursor, uid, parser, result_row_list, prof, statement_id, context)
if comm_vals:
res = statement_line_obj.create(cursor, uid, comm_vals,context=context)
attachment_obj.create(
cursor,

View File

@@ -31,7 +31,7 @@
<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">
<xpath expr="/form/notebook/page/field[@name='line_ids']/form/field[@name='label']" position="after">
<field name="commission_amount" />
</xpath>
</data>

View File

@@ -32,19 +32,19 @@ class CreditPartnerStatementImporter(osv.osv_memory):
_name = "credit.statement.import"
def _get_profile(self, cr, uid, context=None):
def default_get(self, cr, uid, fields, context=None):
if context is None: context = {}
res = False
res = {}
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]
ids = context['active_ids']
assert len(ids) == 1, 'You cannot use this on more than one profile !'
res['profile_id'] = ids[0]
other_vals = self.onchange_profile_id(cr, uid, [], res['profile_id'], context=context)
res.update(other_vals.get('value',{}))
return res
_columns = {
'profile_id': fields.many2one('account.statement.profil',
'Import configuration parameter',
required=True),
@@ -73,12 +73,14 @@ class CreditPartnerStatementImporter(osv.osv_memory):
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,
}
# _defaults = _get_default_values
# {
# 'profile_id': _get_profile,
# }
def onchange_profile_id(self, cr, uid, ids, profile_id, context=None):
res={}
@@ -117,13 +119,6 @@ class CreditPartnerStatementImporter(osv.osv_memory):
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

@@ -8,7 +8,7 @@
<field name="arch" type="xml">
<form string="Import statement">
<group colspan="4" >
<field name="profile_id" />
<field name="profile_id" on_change="onchange_profile_id(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"/>
@@ -38,7 +38,7 @@
<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"/>
<menuitem id="statement_importer_menu" name="Import Bank Statement" action="statement_importer_action" parent="account.menu_finance_bank_and_cash"/>
</data>
</openerp>

View File

@@ -35,6 +35,7 @@
So this way, the reconciliation always happend on the SO name stored in ref.
""",
'website': 'http://www.camptocamp.com',
'init_xml': [],

View File

@@ -47,7 +47,7 @@ class AccountStatementCompletionRule(Model):
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.
the statement line or {}
{'partner_id': value,
'account_id' : value,
...}
@@ -64,6 +64,7 @@ class AccountStatementCompletionRule(Model):
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))
if so_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)

View File

@@ -10,7 +10,7 @@
<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">
<xpath expr="/form/notebook/page/field[@name='line_ids']/form/field[@name='label']" position="after">
<field name="transaction_id" />
</xpath>
</data>

View File

@@ -0,0 +1,21 @@
# -*- 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 parser

View File

@@ -0,0 +1,60 @@
# -*- 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/>.
#
##############################################################################
{'name': "Bank statement transactionID import",
'version': '1.0',
'author': 'Camptocamp',
'maintainer': 'Camptocamp',
'category': 'Finance',
'complexity': 'normal', #easy, normal, expert
'depends': ['account_statement_base_import','account_statement_transactionid_completion'],
'description': """
This module brings generic methods and fields on bank statement to deal with
the importation of different bank and offices that uses transactionID.
This module 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 generate the entries.
This module can handle a commission taken by the payment office and has the following format:
* transactionID : the transaction ID given by the bank/office. 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',
'init_xml': [],
'update_xml': [
],
'demo_xml': [],
'test': [],
'installable': True,
'images': [],
'auto_install': False,
'license': 'AGPL-3',
'active': False,
}

View File

@@ -0,0 +1,4 @@
"transaction_id";"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 transaction_id 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

View File

@@ -0,0 +1,24 @@
# -*- 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 transactionid_file_parser

View File

@@ -0,0 +1,97 @@
# -*- 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 account_statement_base_import.parser.file_parser import FileParser
try:
import xlrd
except:
raise Exception(_('Please install python lib xlrd'))
class TransactionIDFileParser(FileParser):
"""TransactionID parser that use a define format in csv or xls to import
bank statement."""
def __init__(self, parse_name, ftype='csv'):
convertion_dict = {
'transaction_id': 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 = ['transaction_id', 'label', 'date', 'amount', 'commission_amount']
super(TransactionIDFileParser,self).__init__(parse_name, keys_to_validate=keys_to_validate, ftype=ftype, convertion_dict=convertion_dict)
@classmethod
def parser_for(cls, parser_name):
return parser_name == 'generic_csvxls_transaction'
def get_st_line_vals(self, line, *args, **kwargs):
"""This method must return a dict of vals that can be passed to create
method of statement line in order to record it. It is the responsibility
of every parser to give this dict of vals, so each one can implement his
own way of recording the lines.
:param: line: a dict of vals that represent a line of result_row_list
:return: dict of values to give to the create method of statement line,
it MUST contain at least:
{
'name':value,
'date':value,
'amount':value,
'ref':value,
'label':value,
'commission_amount':value,
}
In this generic parser, the commission is given for every line, so we store it
for each one.
"""
return {
'name': line.get('label', line.get('ref','/')),
'date': line.get('date', datetime.datetime.now().date()),
'amount': line.get('amount', 0.0),
'ref': line.get('transaction_id','/'),
'label': line.get('label',''),
'transaction_id': line.get('transaction_id','/'),
'commission_amount': line.get('commission_amount', 0.0),
}
def _post(self, *args, **kwargs):
"""Compute the commission from value of each line"""
res = super(GenericFileParser, self)._post(*args, **kwargs)
val = 0.0
for row in self.result_row_list:
val += row.get('commission_amount',0.0)
self.commission_global_amount = val
return res