mirror of
https://github.com/OCA/account-reconcile.git
synced 2025-01-20 12:27:39 +02:00
[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:
@@ -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': [],
|
||||
|
||||
23
account_statement_base_completion/data.xml
Normal file
23
account_statement_base_completion/data.xml
Normal 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>
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -18,5 +18,6 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import parser
|
||||
import wizard
|
||||
import statement
|
||||
@@ -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',
|
||||
|
||||
4
account_statement_base_import/datas/statement.csv
Normal file
4
account_statement_base_import/datas/statement.csv
Normal 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"
|
||||
|
BIN
account_statement_base_import/datas/statement.xls
Normal file
BIN
account_statement_base_import/datas/statement.xls
Normal file
Binary file not shown.
25
account_statement_base_import/parser/__init__.py
Normal file
25
account_statement_base_import/parser/__init__.py
Normal 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
|
||||
141
account_statement_base_import/parser/file_parser.py
Normal file
141
account_statement_base_import/parser/file_parser.py
Normal 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
|
||||
61
account_statement_base_import/parser/generic_file_parser.py
Normal file
61
account_statement_base_import/parser/generic_file_parser.py
Normal 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'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
151
account_statement_base_import/parser/parser.py
Normal file
151
account_statement_base_import/parser/parser.py
Normal 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
|
||||
|
||||
@@ -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'),
|
||||
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
20
account_statement_base_import/wizard/__init__.py
Normal file
20
account_statement_base_import/wizard/__init__.py
Normal 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
|
||||
128
account_statement_base_import/wizard/import_statement.py
Normal file
128
account_statement_base_import/wizard/import_statement.py
Normal 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'}
|
||||
@@ -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>
|
||||
@@ -19,8 +19,6 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import file_parser
|
||||
import wizard
|
||||
import statement
|
||||
import report
|
||||
import account
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
13
account_statement_transactionid_completion/data.xml
Normal file
13
account_statement_transactionid_completion/data.xml
Normal 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>
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user