[FIX] pep8, pylint, eyeballing

This commit is contained in:
Guewen Baconnier @ Camptocamp
2012-12-20 14:37:01 +01:00
parent a5979a9f45
commit 412d86809d
36 changed files with 673 additions and 540 deletions

View File

@@ -117,4 +117,3 @@ class easy_reconcile_advanced_ref(TransientModel):
yield ('partner_id', move_line['partner_id']) yield ('partner_id', move_line['partner_id'])
yield ('ref', (move_line['ref'].lower().strip(), yield ('ref', (move_line['ref'].lower().strip(),
move_line['name'].lower().strip())) move_line['name'].lower().strip()))

View File

@@ -271,4 +271,3 @@ class easy_reconcile_advanced(AbstractModel):
partial_reconciled_ids += reconcile_group_ids partial_reconciled_ids += reconcile_group_ids
return reconciled_ids, partial_reconciled_ids return reconciled_ids, partial_reconciled_ids

View File

@@ -34,4 +34,3 @@ class account_easy_reconcile_method(Model):
'Advanced. Partner and Ref.'), 'Advanced. Partner and Ref.'),
] ]
return methods return methods

View File

@@ -20,4 +20,4 @@
############################################################################## ##############################################################################
import statement import statement
import partner import partner

View File

@@ -24,7 +24,7 @@
'author': 'Camptocamp', 'author': 'Camptocamp',
'maintainer': 'Camptocamp', 'maintainer': 'Camptocamp',
'category': 'Finance', 'category': 'Finance',
'complexity': 'normal', #easy, normal, expert 'complexity': 'normal',
'depends': ['account_statement_ext'], 'depends': ['account_statement_ext'],
'description': """ 'description': """
The goal of this module is to improve the basic bank statement, help dealing with huge volume of The goal of this module is to improve the basic bank statement, help dealing with huge volume of

View File

@@ -1,4 +1,4 @@
# -*- encoding: utf-8 -*- # -*- coding: utf-8 -*-
################################################################################# #################################################################################
# # # #
# Copyright (C) 2011 Akretion & Camptocamp # Copyright (C) 2011 Akretion & Camptocamp
@@ -31,10 +31,8 @@ class res_partner(Model):
_inherit = 'res.partner' _inherit = 'res.partner'
_columns = { _columns = {
'bank_statement_label':fields.char('Bank Statement Label', size=100, 'bank_statement_label': fields.char('Bank Statement Label', size=100,
help="Enter the various label found on your bank statement separated by a ; If \ help="Enter the various label found on your bank statement separated by a ; If \
one of this label is include in the bank statement line, the partner will be automatically \ one of this label is include in the bank statement line, the partner will be automatically \
filled (as long as you use this method/rules in your statement profile)."), filled (as long as you use this method/rules in your statement profile)."),
} }
res_partner()

View File

@@ -20,10 +20,11 @@
############################################################################## ##############################################################################
from tools.translate import _ from tools.translate import _
from openerp.osv.orm import Model, fields from openerp.osv.orm import Model, fields
from openerp.osv import fields, osv, osv from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
from operator import itemgetter, attrgetter from operator import attrgetter
import datetime import datetime
class ErrorTooManyPartner(Exception): class ErrorTooManyPartner(Exception):
""" """
New Exception definition that is raised when more than one partner is matched by New Exception definition that is raised when more than one partner is matched by
@@ -31,6 +32,7 @@ class ErrorTooManyPartner(Exception):
""" """
def __init__(self, value): def __init__(self, value):
self.value = value self.value = value
def __str__(self): def __str__(self):
return repr(self.value) return repr(self.value)
@@ -40,38 +42,38 @@ class AccountStatementProfil(Model):
Extend the class to add rules per profile that will match at least the partner, Extend the class to add rules per profile that will match at least the partner,
but it could also be used to match other values as well. but it could also be used to match other values as well.
""" """
_inherit = "account.statement.profile" _inherit = "account.statement.profile"
_columns={ _columns = {
# @Akretion : For now, we don't implement this features, but this would probably be there: # @Akretion : For now, we don't implement this features, but this would probably be there:
# 'auto_completion': fields.text('Auto Completion'), # 'auto_completion': fields.text('Auto Completion'),
# 'transferts_account_id':fields.many2one('account.account', 'Transferts Account'), # 'transferts_account_id':fields.many2one('account.account', 'Transferts Account'),
# => You can implement it in a module easily, we design it with your needs in mind # => You can implement it in a module easily, we design it with your needs in mind
# as well ! # as well !
'rule_ids':fields.many2many('account.statement.completion.rule', 'rule_ids': fields.many2many(
'account.statement.completion.rule',
string='Related statement profiles', string='Related statement profiles',
rel='as_rul_st_prof_rel', rel='as_rul_st_prof_rel'),
),
} }
def find_values_from_rules(self, cr, uid, id, line_id, context=None): def find_values_from_rules(self, cr, uid, id, line_id, context=None):
""" """
This method will execute all related rules, in their sequence order, This method will execute all related rules, in their sequence order,
to retrieve all the values returned by the first rules that will match. to retrieve all the values returned by the first rules that will match.
:param int/long line_id: id of the concerned account.bank.statement.line :param int/long line_id: id of the concerned account.bank.statement.line
:return: :return:
A dict of value that can be passed directly to the write method of A dict of value that can be passed directly to the write method of
the statement line or {} the statement line or {}
{'partner_id': value, {'partner_id': value,
'account_id' : value, 'account_id' : value,
...} ...}
""" """
if not context: if context is None:
context={} context = {}
res = {} res = {}
rule_obj = self.pool.get('account.statement.completion.rule') rule_obj = self.pool.get('account.statement.completion.rule')
profile = self.browse(cr, uid, id, context=context) profile = self.browse(cr, uid, id, context=context)
@@ -79,11 +81,11 @@ class AccountStatementProfil(Model):
sorted_array = sorted(profile.rule_ids, key=attrgetter('sequence')) sorted_array = sorted(profile.rule_ids, key=attrgetter('sequence'))
for rule in sorted_array: for rule in sorted_array:
method_to_call = getattr(rule_obj, rule.function_to_call) method_to_call = getattr(rule_obj, rule.function_to_call)
result = method_to_call(cr,uid,line_id,context) result = method_to_call(cr, uid, line_id, context)
if result: if result:
return result return result
return res return res
class AccountStatementCompletionRule(Model): class AccountStatementCompletionRule(Model):
""" """
@@ -91,14 +93,14 @@ class AccountStatementCompletionRule(Model):
fullfill the bank statement lines. You'll be able to extend them in you own module fullfill the bank statement lines. 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 rule is to fullfill at least the partner of the line, but The goal of a rule is to fullfill at least the partner of the line, but
if possible also the reference because we'll use it in the reconciliation if possible also the reference because we'll use it in the reconciliation
process. The reference should contain the invoice number or the SO number process. The reference should contain the invoice number or the SO number
or any reference that will be matched by the invoice accounting move. or any reference that will be matched by the invoice accounting move.
""" """
_name = "account.statement.completion.rule" _name = "account.statement.completion.rule"
_order = "sequence asc" _order = "sequence asc"
def _get_functions(self, cr, uid, context=None): def _get_functions(self, cr, uid, context=None):
""" """
List of available methods for rules. Override this to add you own. List of available methods for rules. Override this to add you own.
@@ -109,19 +111,20 @@ class AccountStatementCompletionRule(Model):
('get_from_label_and_partner_field', 'From line label (based on partner field)'), ('get_from_label_and_partner_field', 'From line label (based on partner field)'),
('get_from_label_and_partner_name', 'From line label (based on partner name)'), ('get_from_label_and_partner_name', 'From line label (based on partner name)'),
] ]
_columns={ _columns = {
'sequence': fields.integer('Sequence', help="Lower means parsed first."), 'sequence': fields.integer('Sequence', help="Lower means parsed first."),
'name': fields.char('Name', size=128), 'name': fields.char('Name', size=128),
'profile_ids': fields.many2many('account.statement.profile', 'profile_ids': fields.many2many(
rel='as_rul_st_prof_rel', 'account.statement.profile',
rel='as_rul_st_prof_rel',
string='Related statement profiles'), string='Related statement profiles'),
'function_to_call': fields.selection(_get_functions, 'Method'), 'function_to_call': fields.selection(_get_functions, 'Method'),
} }
def get_from_ref_and_invoice(self, cursor, uid, line_id, context=None): def get_from_ref_and_invoice(self, cursor, uid, line_id, context=None):
""" """
Match the partner based on the invoice number and the reference of the statement Match the partner based on the invoice number and the reference of the statement
line. Then, call the generic get_values_for_line method to complete other values. line. Then, call the generic get_values_for_line method to complete other values.
If more than one partner matched, raise the ErrorTooManyPartner error. If more than one partner matched, raise the ErrorTooManyPartner error.
@@ -131,30 +134,42 @@ class AccountStatementCompletionRule(Model):
the statement line or {} the statement line or {}
{'partner_id': value, {'partner_id': value,
'account_id' : value, 'account_id' : value,
...} ...}
""" """
st_obj = self.pool.get('account.bank.statement.line') st_obj = self.pool.get('account.bank.statement.line')
st_line = st_obj.browse(cursor,uid,line_id) st_line = st_obj.browse(cursor, uid, line_id, context=context)
res = {} res = {}
if st_line: if st_line:
inv_obj = self.pool.get('account.invoice') inv_obj = self.pool.get('account.invoice')
inv_id = inv_obj.search(cursor, uid, [('number', '=', st_line.ref)]) inv_id = inv_obj.search(
cursor,
uid,
[('number', '=', st_line.ref)],
context=context)
if inv_id: if inv_id:
if inv_id and len(inv_id) == 1: if inv_id and len(inv_id) == 1:
inv = inv_obj.browse(cursor, uid, inv_id[0]) inv = inv_obj.browse(cursor, uid, inv_id[0], context=context)
res['partner_id'] = inv.partner_id.id res['partner_id'] = inv.partner_id.id
elif inv_id and len(inv_id) > 1: elif inv_id and len(inv_id) > 1:
raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more than one partner.')%(st_line.name,st_line.ref)) raise ErrorTooManyPartner(
st_vals = st_obj.get_values_for_line(cursor, uid, profile_id = st_line.statement_id.profile_id.id, _('Line named "%s" (Ref:%s) was matched by more '
partner_id = res.get('partner_id',False), line_type = st_line.type, amount = st_line.amount, context = context) 'than one partner.') % (st_line.name, st_line.ref))
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) res.update(st_vals)
return res return res
def get_from_ref_and_so(self, cursor, uid, line_id, context=None): def get_from_ref_and_so(self, cursor, uid, line_id, context=None):
""" """
Match the partner based on the SO number and the reference of the statement Match the partner based on the SO number and the reference of the statement
line. Then, call the generic get_values_for_line method to complete other values. line. Then, call the generic get_values_for_line method to complete other values.
If more than one partner matched, raise the ErrorTooManyPartner error. If more than one partner matched, raise the ErrorTooManyPartner error.
:param int/long line_id: id of the concerned account.bank.statement.line :param int/long line_id: id of the concerned account.bank.statement.line
@@ -163,32 +178,44 @@ class AccountStatementCompletionRule(Model):
the statement line or {} the statement line or {}
{'partner_id': value, {'partner_id': value,
'account_id' : value, 'account_id' : value,
...} ...}
""" """
st_obj = self.pool.get('account.bank.statement.line') st_obj = self.pool.get('account.bank.statement.line')
st_line = st_obj.browse(cursor,uid,line_id) st_line = st_obj.browse(cursor, uid, line_id, context=context)
res = {} res = {}
if st_line: if st_line:
so_obj = self.pool.get('sale.order') so_obj = self.pool.get('sale.order')
so_id = so_obj.search(cursor, uid, [('name', '=', st_line.ref)]) so_id = so_obj.search(
cursor,
uid,
[('name', '=', st_line.ref)],
context=context)
if so_id: if so_id:
if so_id and len(so_id) == 1: if so_id and len(so_id) == 1:
so = so_obj.browse(cursor, uid, so_id[0]) so = so_obj.browse(cursor, uid, so_id[0], context=context)
res['partner_id'] = so.partner_id.id res['partner_id'] = so.partner_id.id
elif so_id and len(so_id) > 1: elif so_id and len(so_id) > 1:
raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more than one partner.')%(st_line.name,st_line.ref)) raise ErrorTooManyPartner(
st_vals = st_obj.get_values_for_line(cursor, uid, profile_id = st_line.statement_id.profile_id.id, _('Line named "%s" (Ref:%s) was matched by more '
partner_id = res.get('partner_id',False), line_type = st_line.type, amount = st_line.amount, context = context) 'than one partner.') %
(st_line.name, st_line.ref))
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) res.update(st_vals)
return res return res
def get_from_label_and_partner_field(self, cursor, uid, line_id, context=None): def get_from_label_and_partner_field(self, cursor, uid, line_id, context=None):
""" """
Match the partner based on the label field of the statement line Match the partner based on the label field of the statement line
and the text defined in the 'bank_statement_label' field of the partner. and the text defined in the 'bank_statement_label' field of the partner.
Remember that we can have values separated with ; Then, call the generic Remember that we can have values separated with ; Then, call the generic
get_values_for_line method to complete other values. get_values_for_line method to complete other values.
If more than one partner matched, raise the ErrorTooManyPartner error. If more than one partner matched, raise the ErrorTooManyPartner error.
@@ -198,26 +225,39 @@ class AccountStatementCompletionRule(Model):
the statement line or {} the statement line or {}
{'partner_id': value, {'partner_id': value,
'account_id' : value, 'account_id' : value,
...} ...}
""" """
partner_obj = self.pool.get('res.partner') partner_obj = self.pool.get('res.partner')
st_obj = self.pool.get('account.bank.statement.line') st_obj = self.pool.get('account.bank.statement.line')
st_line = st_obj.browse(cursor,uid,line_id) st_line = st_obj.browse(cursor, uid, line_id, context=context)
res = {} res = {}
compt = 0 compt = 0
if st_line: if st_line:
ids = partner_obj.search(cursor, uid, [['bank_statement_label', '!=', False]], context=context) ids = partner_obj.search(
cursor,
uid,
[('bank_statement_label', '!=', False)],
context=context)
for partner in partner_obj.browse(cursor, uid, ids, context=context): for partner in partner_obj.browse(cursor, uid, ids, context=context):
for partner_label in partner.bank_statement_label.split(';'): for partner_label in partner.bank_statement_label.split(';'):
if partner_label in st_line.label: if partner_label in st_line.label:
compt += 1 compt += 1
res['partner_id'] = partner.id res['partner_id'] = partner.id
if compt > 1: if compt > 1:
raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more than one partner.')%(st_line.name,st_line.ref)) raise ErrorTooManyPartner(
_('Line named "%s" (Ref:%s) was matched by '
'more than one partner.') %
(st_line.name, st_line.ref))
if res: if res:
st_vals = st_obj.get_values_for_line(cursor, uid, profile_id = st_line.statement_id.profile_id.id, st_vals = st_obj.get_values_for_line(
partner_id = res.get('partner_id',False), line_type = st_line.type, amount = st_line.amount, context = context) 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) res.update(st_vals)
return res return res
@@ -234,29 +274,38 @@ class AccountStatementCompletionRule(Model):
the statement line or {} the statement line or {}
{'partner_id': value, {'partner_id': value,
'account_id' : value, 'account_id' : value,
...} ...}
""" """
# This Method has not been tested yet ! # This Method has not been tested yet !
res = {} res = {}
st_obj = self.pool.get('account.bank.statement.line') st_obj = self.pool.get('account.bank.statement.line')
st_line = st_obj.browse(cursor,uid,line_id) st_line = st_obj.browse(cursor, uid, line_id, context=context)
if st_line: if st_line:
sql = "SELECT id FROM res_partner WHERE name ~* %s" sql = "SELECT id FROM res_partner WHERE name ~* %s"
pattern = ".*%s.*" % st_line.label pattern = ".*%s.*" % st_line.label
cursor.execute(sql, (pattern,)) cursor.execute(sql, (pattern,))
result = cursor.fetchall() result = cursor.fetchall()
if len(result) > 1: if len(result) > 1:
raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more than one partner.')%(st_line.name,st_line.ref)) raise ErrorTooManyPartner(
_('Line named "%s" (Ref:%s) was matched by more '
'than one partner.') %
(st_line.name, st_line.ref))
for id in result[0]: for id in result[0]:
res['partner_id'] = id res['partner_id'] = id
if res: if res:
st_vals = st_obj.get_values_for_line(cursor, uid, profile_id = st_line.statement_id.profile_id.id, st_vals = st_obj.get_values_for_line(
partner_id = res.get('partner_id',False), line_type = st_line.type, amount = st_line.amount, context = context) 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) res.update(st_vals)
return res return res
class AccountStatementLine(Model): class AccountStatementLine(Model):
""" """
Add sparse field on the statement line to allow to store all the Add sparse field on the statement line to allow to store all the
@@ -268,22 +317,28 @@ class AccountStatementLine(Model):
""" """
_inherit = "account.bank.statement.line" _inherit = "account.bank.statement.line"
_columns={ _columns = {
'additionnal_bank_fields' : fields.serialized('Additionnal infos from bank', 'additionnal_bank_fields': fields.serialized(
help="Used by completion and import system. Adds every field that is present in your bank/office \ 'Additionnal infos from bank',
statement file"), help="Used by completion and import system. Adds every field that "
'label': fields.sparse(type='char', string='Label', "is present in your bank/office statement file"),
serialization_field='additionnal_bank_fields', 'label': fields.sparse(
help="Generiy field to store a label given from the bank/office on which we can \ type='char',
base the default/standard providen rule."), string='Label',
'already_completed': fields.boolean("Auto-Completed", serialization_field='additionnal_bank_fields',
help="When this checkbox is ticked, the auto-completion process/button will ignore this line."), help="Generic field to store a label given from the "
"bank/office on which we can base the default/standard "
"providen rule."),
'already_completed': fields.boolean(
"Auto-Completed",
help="When this checkbox is ticked, the auto-completion "
"process/button will ignore this line."),
} }
_defaults = { _defaults = {
'already_completed': False, 'already_completed': False,
} }
def get_line_values_from_rules(self, cr, uid, ids, context=None): def get_line_values_from_rules(self, cr, uid, ids, context=None):
""" """
We'll try to find out the values related to the line based on rules setted on We'll try to find out the values related to the line based on rules setted on
@@ -292,30 +347,38 @@ class AccountStatementLine(Model):
:return: :return:
A dict of dict value that can be passed directly to the write method of A dict of dict value that can be passed directly to the write method of
the statement line or {}. The first dict has statement line ID as a key: the statement line or {}. The first dict has statement line ID as a key:
{117009: {'partner_id': 100997, 'account_id': 489L}} {117009: {'partner_id': 100997, 'account_id': 489L}}
""" """
profile_obj = self.pool.get('account.statement.profile') profile_obj = self.pool.get('account.statement.profile')
st_obj = self.pool.get('account.bank.statement.line') st_obj = self.pool.get('account.bank.statement.line')
res={} res = {}
errors_stack = [] errors_stack = []
for line in self.browse(cr,uid, ids, context): for line in self.browse(cr, uid, ids, context=context):
if not line.already_completed: if line.already_completed:
try: continue
# Take the default values try:
res[line.id] = st_obj.get_values_for_line(cr, uid, profile_id = line.statement_id.profile_id.id, # Take the default values
line_type = line.type, amount = line.amount, context = context) res[line.id] = st_obj.get_values_for_line(
# Ask the rule cr,
vals = profile_obj.find_values_from_rules(cr, uid, line.statement_id.profile_id.id, line.id, context) uid,
# Merge the result profile_id=line.statement_id.profile_id.id,
res[line.id].update(vals) line_type=line.type,
except ErrorTooManyPartner, exc: amount=line.amount,
msg = "Line ID %s had following error: %s" % (line.id, exc.value) context=context)
errors_stack.append(msg) # Ask the rule
vals = profile_obj.find_values_from_rules(
cr, uid, line.statement_id.profile_id.id, line.id, context)
# Merge the result
res[line.id].update(vals)
except ErrorTooManyPartner, exc:
msg = "Line ID %s had following error: %s" % (line.id, exc.value)
errors_stack.append(msg)
if errors_stack: if errors_stack:
msg = u"\n".join(errors_stack) msg = u"\n".join(errors_stack)
raise ErrorTooManyPartner(msg) raise ErrorTooManyPartner(msg)
return res return res
class AccountBankSatement(Model): class AccountBankSatement(Model):
""" """
We add a basic button and stuff to support the auto-completion We add a basic button and stuff to support the auto-completion
@@ -326,41 +389,46 @@ class AccountBankSatement(Model):
_columns = { _columns = {
'completion_logs': fields.text('Completion Log', readonly=True), 'completion_logs': fields.text('Completion Log', readonly=True),
} }
def write_completion_log(self, cr, uid, stat_id, error_msg, number_imported, context=None): def write_completion_log(self, cr, uid, stat_id, error_msg, number_imported, context=None):
""" """
Write the log in the completion_logs field of the bank statement to let the user Write the log in the completion_logs field of the bank statement to let the user
know what have been done. This is an append mode, so we don't overwrite what know what have been done. This is an append mode, so we don't overwrite what
already recoded. already recoded.
:param int/long stat_id: ID of the account.bank.statement :param int/long stat_id: ID of the account.bank.statement
:param char error_msg: Message to add :param char error_msg: Message to add
:number_imported int/long: Number of lines that have been completed :number_imported int/long: Number of lines that have been completed
:return : True :return : True
""" """
error_log = "" error_log = ""
user_name = self.pool.get('res.users').read(cr, uid, uid, ['name'])['name'] user_name = self.pool.get('res.users').read(
log = self.read(cr, uid, stat_id, ['completion_logs'], context=context)['completion_logs'] cr, uid, uid, ['name'], context=context)['name']
log = self.read(
cr, uid, stat_id, ['completion_logs'], context=context)['completion_logs']
log_line = log and log.split("\n") or [] log_line = log and log.split("\n") or []
completion_date = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') completion_date = datetime.datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT)
if error_msg: if error_msg:
error_log = error_msg error_log = error_msg
log_line[0:0] = [completion_date + ' : ' log_line[0:0] = [completion_date + ' : '
+ _("Bank Statement ID %s has %s lines completed by %s") %(stat_id, number_imported, user_name) + _("Bank Statement ID %s has %s lines completed by %s") % (stat_id, number_imported, user_name)
+ "\n" + error_log + "-------------" + "\n"] + "\n" + error_log + "-------------" + "\n"]
log = "\n".join(log_line) log = "\n".join(log_line)
self.write(cr, uid, [stat_id], {'completion_logs' : log}, context=context) self.write(cr, uid, [stat_id], {'completion_logs': log}, context=context)
self.message_post(cr, uid, [stat_id], body=_('Statement ID %s auto-completed for %s lines completed') % (stat_id,number_imported), context=context) self.message_post(
cr, uid,
[stat_id],
body=_('Statement ID %s auto-completed for %s lines completed') % (stat_id, number_imported),
context=context)
return True return True
def button_auto_completion(self, cr, uid, ids, context=None): def button_auto_completion(self, cr, uid, ids, context=None):
""" """
Complete line with values given by rules and tic the already_completed Complete line with values given by rules and tic the already_completed
checkbox so we won't compute them again unless the user untick them ! checkbox so we won't compute them again unless the user untick them !
""" """
if not context: if context is None:
context={} context = {}
stat_line_obj = self.pool.get('account.bank.statement.line') stat_line_obj = self.pool.get('account.bank.statement.line')
msg = "" msg = ""
compl_lines = 0 compl_lines = 0
@@ -369,7 +437,8 @@ class AccountBankSatement(Model):
for line in stat.line_ids: for line in stat.line_ids:
res = {} res = {}
try: try:
res = stat_line_obj.get_line_values_from_rules(cr, uid, [line.id], context=ctx) res = stat_line_obj.get_line_values_from_rules(
cr, uid, [line.id], context=ctx)
if res: if res:
compl_lines += 1 compl_lines += 1
except ErrorTooManyPartner, exc: except ErrorTooManyPartner, exc:
@@ -381,5 +450,6 @@ class AccountBankSatement(Model):
vals = res[line.id] vals = res[line.id]
vals['already_completed'] = True vals['already_completed'] = True
stat_line_obj.write(cr, uid, [line.id], vals, context=ctx) stat_line_obj.write(cr, uid, [line.id], vals, context=ctx)
self.write_completion_log(cr, uid, stat.id, msg, compl_lines, context=context) self.write_completion_log(
cr, uid, stat.id, msg, compl_lines, context=context)
return True return True

View File

@@ -20,4 +20,4 @@
############################################################################## ##############################################################################
import parser import parser
import wizard import wizard
import statement import statement

View File

@@ -24,8 +24,11 @@
'author': 'Camptocamp', 'author': 'Camptocamp',
'maintainer': 'Camptocamp', 'maintainer': 'Camptocamp',
'category': 'Finance', 'category': 'Finance',
'complexity': 'normal', #easy, normal, expert 'complexity': 'normal',
'depends': ['account_statement_ext','account_statement_base_completion'], 'depends': [
'account_statement_ext',
'account_statement_base_completion'
],
'description': """ 'description': """
This module brings basic methods and fields on bank statement to deal with This module brings basic methods and fields on bank statement to deal with
the importation of different bank and offices. A generic abstract method is defined and an the importation of different bank and offices. A generic abstract method is defined and an

View File

@@ -22,4 +22,4 @@
from parser import new_bank_statement_parser from parser import new_bank_statement_parser
from parser import BankStatementImportParser from parser import BankStatementImportParser
import file_parser import file_parser
import generic_file_parser import generic_file_parser

View File

@@ -28,17 +28,18 @@ try:
except: except:
raise Exception(_('Please install python lib xlrd')) raise Exception(_('Please install python lib xlrd'))
class FileParser(BankStatementImportParser): class FileParser(BankStatementImportParser):
""" """
Generic abstract class for defining parser for .csv or .xls file format. Generic abstract class for defining parser for .csv or .xls file format.
""" """
def __init__(self, parse_name, keys_to_validate=[], ftype='csv', convertion_dict=None, header=None, *args, **kwargs): def __init__(self, parse_name, keys_to_validate=[], ftype='csv', convertion_dict=None, header=None, *args, **kwargs):
""" """
:param char: parse_name : The name of the parser :param char: parse_name : The name of the parser
:param list: keys_to_validate : contain the key that need to be present in the file :param list: keys_to_validate : contain the key that need to be present in the file
:param char ftype: extension of the file (could be csv or xls) :param char ftype: extension of the file (could be csv or xls)
:param: convertion_dict : keys and type to convert of every column in the file like :param: convertion_dict : keys and type to convert of every column in the file like
{ {
'ref': unicode, 'ref': unicode,
'label': unicode, 'label': unicode,
@@ -48,7 +49,7 @@ class FileParser(BankStatementImportParser):
} }
:param list: header : specify header fields if the csv file has no header :param list: header : specify header fields if the csv file has no header
""" """
super(FileParser, self).__init__(parse_name, *args, **kwargs) super(FileParser, self).__init__(parse_name, *args, **kwargs)
if ftype in ('csv', 'xls'): if ftype in ('csv', 'xls'):
self.ftype = ftype self.ftype = ftype
@@ -57,9 +58,9 @@ class FileParser(BankStatementImportParser):
self.keys_to_validate = keys_to_validate self.keys_to_validate = keys_to_validate
self.convertion_dict = convertion_dict self.convertion_dict = convertion_dict
self.fieldnames = header self.fieldnames = header
self._datemode = 0 # used only for xls documents, self._datemode = 0 # used only for xls documents,
# 0 means Windows mode (1900 based dates). # 0 means Windows mode (1900 based dates).
# Set in _parse_xls, from the contents of the file # Set in _parse_xls, from the contents of the file
def _custom_format(self, *args, **kwargs): def _custom_format(self, *args, **kwargs):
""" """
@@ -78,7 +79,7 @@ class FileParser(BankStatementImportParser):
Launch the parsing through .csv or .xls depending on the Launch the parsing through .csv or .xls depending on the
given ftype given ftype
""" """
res = None res = None
if self.ftype == 'csv': if self.ftype == 'csv':
res = self._parse_csv() res = self._parse_csv()
@@ -108,7 +109,6 @@ class FileParser(BankStatementImportParser):
self.result_row_list = self._cast_rows(*args, **kwargs) self.result_row_list = self._cast_rows(*args, **kwargs)
return True return True
def _parse_csv(self): def _parse_csv(self):
""" """
:return: list of dict from csv file (line/rows) :return: list of dict from csv file (line/rows)
@@ -116,7 +116,7 @@ class FileParser(BankStatementImportParser):
csv_file = tempfile.NamedTemporaryFile() csv_file = tempfile.NamedTemporaryFile()
csv_file.write(self.filebuffer) csv_file.write(self.filebuffer)
csv_file.flush() csv_file.flush()
with open(csv_file.name, 'rU') as fobj: with open(csv_file.name, 'rU') as fobj:
reader = UnicodeDictReader(fobj, fieldnames=self.fieldnames) reader = UnicodeDictReader(fobj, fieldnames=self.fieldnames)
return list(reader) return list(reader)
@@ -138,7 +138,7 @@ class FileParser(BankStatementImportParser):
try: try:
wb_file.close() wb_file.close()
except Exception, e: except Exception, e:
pass #file is allready closed pass # file is already closed
return res return res
def _from_csv(self, result_set, conversion_rules): def _from_csv(self, result_set, conversion_rules):
@@ -175,6 +175,6 @@ class FileParser(BankStatementImportParser):
Convert the self.result_row_list using the self.convertion_dict providen. Convert the self.result_row_list using the self.convertion_dict providen.
We call here _from_xls or _from_csv depending on the self.ftype variable. We call here _from_xls or _from_csv depending on the self.ftype variable.
""" """
func = getattr(self, '_from_%s'%(self.ftype)) func = getattr(self, '_from_%s' % self.ftype)
res = func(self.result_row_list, self.convertion_dict) res = func(self.result_row_list, self.convertion_dict)
return res return res

View File

@@ -23,20 +23,20 @@ import base64
import csv import csv
import tempfile import tempfile
import datetime import datetime
# from . import file_parser
from file_parser import FileParser from file_parser import FileParser
try: try:
import xlrd import xlrd
except: except:
raise Exception(_('Please install python lib xlrd')) raise Exception(_('Please install python lib xlrd'))
class GenericFileParser(FileParser): class GenericFileParser(FileParser):
""" """
Standard parser that use a define format in csv or xls to import into a Standard parser that use a define format in csv or xls to import into a
bank statement. This is mostely an example of how to proceed to create a new bank statement. This is mostely an example of how to proceed to create a new
parser, but will also be useful as it allow to import a basic flat file. parser, but will also be useful as it allow to import a basic flat file.
""" """
def __init__(self, parse_name, ftype='csv'): def __init__(self, parse_name, ftype='csv'):
convertion_dict = { convertion_dict = {
'ref': unicode, 'ref': unicode,
@@ -47,7 +47,7 @@ class GenericFileParser(FileParser):
} }
# Order of cols does not matter but first row of the file has to be header # 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'] keys_to_validate = ['ref', 'label', 'date', 'amount', 'commission_amount']
super(GenericFileParser,self).__init__(parse_name, keys_to_validate=keys_to_validate, ftype=ftype, convertion_dict=convertion_dict) super(GenericFileParser, self).__init__(parse_name, keys_to_validate=keys_to_validate, ftype=ftype, convertion_dict=convertion_dict)
@classmethod @classmethod
def parser_for(cls, parser_name): def parser_for(cls, parser_name):
@@ -60,7 +60,7 @@ class GenericFileParser(FileParser):
def get_st_line_vals(self, line, *args, **kwargs): def get_st_line_vals(self, line, *args, **kwargs):
""" """
This method must return a dict of vals that can be passed to create 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 method of statement line in order to record it. It is the responsibility
of every parser to give this dict of vals, so each one can implement his of every parser to give this dict of vals, so each one can implement his
own way of recording the lines. own way of recording the lines.
:param: line: a dict of vals that represent a line of result_row_list :param: line: a dict of vals that represent a line of result_row_list
@@ -78,11 +78,11 @@ class GenericFileParser(FileParser):
for each one. for each one.
""" """
return { return {
'name': line.get('label', line.get('ref','/')), 'name': line.get('label', line.get('ref', '/')),
'date': line.get('date', datetime.datetime.now().date()), 'date': line.get('date', datetime.datetime.now().date()),
'amount': line.get('amount', 0.0), 'amount': line.get('amount', 0.0),
'ref': line.get('ref','/'), 'ref': line.get('ref', '/'),
'label': line.get('label',''), 'label': line.get('label', ''),
'commission_amount': line.get('commission_amount', 0.0), 'commission_amount': line.get('commission_amount', 0.0),
} }
@@ -93,10 +93,6 @@ class GenericFileParser(FileParser):
res = super(GenericFileParser, self)._post(*args, **kwargs) res = super(GenericFileParser, self)._post(*args, **kwargs)
val = 0.0 val = 0.0
for row in self.result_row_list: for row in self.result_row_list:
val += row.get('commission_amount',0.0) val += row.get('commission_amount', 0.0)
self.commission_global_amount = val self.commission_global_amount = val
return res return res

View File

@@ -21,6 +21,7 @@
import base64 import base64
import csv import csv
def UnicodeDictReader(utf8_data, **kwargs): def UnicodeDictReader(utf8_data, **kwargs):
sniffer = csv.Sniffer() sniffer = csv.Sniffer()
pos = utf8_data.tell() pos = utf8_data.tell()
@@ -31,6 +32,7 @@ def UnicodeDictReader(utf8_data, **kwargs):
for row in csv_reader: for row in csv_reader:
yield dict([(key, unicode(value, 'utf-8')) for key, value in row.iteritems()]) yield dict([(key, unicode(value, 'utf-8')) for key, value in row.iteritems()])
class BankStatementImportParser(object): class BankStatementImportParser(object):
""" """
Generic abstract class for defining parser for different files and Generic abstract class for defining parser for different files and
@@ -38,7 +40,7 @@ class BankStatementImportParser(object):
own. If your file is a .csv or .xls format, you should consider inheirt own. If your file is a .csv or .xls format, you should consider inheirt
from the FileParser instead. from the FileParser instead.
""" """
def __init__(self, parser_name, *args, **kwargs): def __init__(self, parser_name, *args, **kwargs):
# The name of the parser as it will be called # The name of the parser as it will be called
self.parser_name = parser_name self.parser_name = parser_name
@@ -50,7 +52,7 @@ class BankStatementImportParser(object):
# Concatenate here the global commission taken by the bank/office # Concatenate here the global commission taken by the bank/office
# for this statement. # for this statement.
self.commission_global_amount = None self.commission_global_amount = None
@classmethod @classmethod
def parser_for(cls, parser_name): def parser_for(cls, parser_name):
""" """
@@ -58,17 +60,17 @@ class BankStatementImportParser(object):
return the good class from his name. return the good class from his name.
""" """
return False return False
def _decode_64b_stream(self): def _decode_64b_stream(self):
""" """
Decode self.filebuffer in base 64 and override it Decode self.filebuffer in base 64 and override it
""" """
self.filebuffer = base64.b64decode(self.filebuffer) self.filebuffer = base64.b64decode(self.filebuffer)
return True return True
def _format(self, decode_base_64=True, **kwargs): def _format(self, decode_base_64=True, **kwargs):
""" """
Decode into base 64 if asked and Format the given filebuffer by calling Decode into base 64 if asked and Format the given filebuffer by calling
_custom_format method. _custom_format method.
""" """
if decode_base_64: if decode_base_64:
@@ -83,43 +85,40 @@ class BankStatementImportParser(object):
""" """
return NotImplementedError return NotImplementedError
def _pre(self, *args, **kwargs): def _pre(self, *args, **kwargs):
""" """
Implement a method in your parser to make a pre-treatment on datas before parsing Implement a method in your parser to make a pre-treatment on datas before parsing
them, like concatenate stuff, and so... Work on self.filebuffer them, like concatenate stuff, and so... Work on self.filebuffer
""" """
return NotImplementedError return NotImplementedError
def _parse(self, *args, **kwargs): def _parse(self, *args, **kwargs):
""" """
Implement a method in your parser to save the result of parsing self.filebuffer Implement a method in your parser to save the result of parsing self.filebuffer
in self.result_row_list instance property. in self.result_row_list instance property.
""" """
return NotImplementedError return NotImplementedError
def _validate(self, *args, **kwargs): def _validate(self, *args, **kwargs):
""" """
Implement a method in your parser to validate the self.result_row_list instance Implement a method in your parser to validate the self.result_row_list instance
property and raise an error if not valid. property and raise an error if not valid.
""" """
return NotImplementedError return NotImplementedError
def _post(self, *args, **kwargs): def _post(self, *args, **kwargs):
""" """
Implement a method in your parser to make some last changes on the result of parsing Implement a method in your parser to make some last changes on the result of parsing
the datas, like converting dates, computing commission, ... the datas, like converting dates, computing commission, ...
Work on self.result_row_list and put the commission global amount if any Work on self.result_row_list and put the commission global amount if any
in the self.commission_global_amount one. in the self.commission_global_amount one.
""" """
return NotImplementedError return NotImplementedError
def get_st_line_vals(self, line, *args, **kwargs): def get_st_line_vals(self, line, *args, **kwargs):
""" """
Implement a method in your parser that must return a dict of vals that can be Implement a method in your parser that 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 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 of every parser to give this dict of vals, so each one can implement his
own way of recording the lines. own way of recording the lines.
:param: line: a dict of vals that represent a line of result_row_list :param: line: a dict of vals that represent a line of result_row_list
@@ -133,17 +132,17 @@ class BankStatementImportParser(object):
} }
""" """
return NotImplementedError return NotImplementedError
def get_st_line_commision(self, *args, **kwargs): def get_st_line_commision(self, *args, **kwargs):
""" """
This is called by the importation method to create the commission line in 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 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 bank statement, but it could be computated from a value of each line, or given
in a single line for the whole file. in a single line for the whole file.
return: float of the whole commission (self.commission_global_amount) return: float of the whole commission (self.commission_global_amount)
""" """
return self.commission_global_amount return self.commission_global_amount
def parse(self, filebuffer, *args, **kwargs): def parse(self, filebuffer, *args, **kwargs):
""" """
This will be the method that will be called by wizard, button and so This will be the method that will be called by wizard, button and so
@@ -151,7 +150,7 @@ class BankStatementImportParser(object):
that need to be define for each parser. that need to be define for each parser.
Return: Return:
[] of rows as {'key':value} [] of rows as {'key':value}
Note: The row_list must contain only value that are present in the account. Note: The row_list must contain only value that are present in the account.
bank.statement.line object !!! bank.statement.line object !!!
""" """
@@ -165,7 +164,8 @@ class BankStatementImportParser(object):
self._validate(*args, **kwargs) self._validate(*args, **kwargs)
self._post(*args, **kwargs) self._post(*args, **kwargs)
return self.result_row_list return self.result_row_list
def itersubclasses(cls, _seen=None): def itersubclasses(cls, _seen=None):
""" """
itersubclasses(cls) itersubclasses(cls)
@@ -179,7 +179,7 @@ def itersubclasses(cls, _seen=None):
>>> class C(A): pass >>> class C(A): pass
>>> class D(B,C): pass >>> class D(B,C): pass
>>> class E(D): pass >>> class E(D): pass
>>> >>>
>>> for cls in itersubclasses(A): >>> for cls in itersubclasses(A):
... print(cls.__name__) ... print(cls.__name__)
B B
@@ -193,10 +193,11 @@ def itersubclasses(cls, _seen=None):
if not isinstance(cls, type): if not isinstance(cls, type):
raise TypeError('itersubclasses must be called with ' raise TypeError('itersubclasses must be called with '
'new-style classes, not %.100r' % cls) 'new-style classes, not %.100r' % cls)
if _seen is None: _seen = set() if _seen is None:
_seen = set()
try: try:
subs = cls.__subclasses__() subs = cls.__subclasses__()
except TypeError: # fails only when cls is type except TypeError: # fails only when cls is type
subs = cls.__subclasses__(cls) subs = cls.__subclasses__(cls)
for sub in subs: for sub in subs:
if sub not in _seen: if sub not in _seen:
@@ -204,7 +205,8 @@ def itersubclasses(cls, _seen=None):
yield sub yield sub
for sub in itersubclasses(sub, _seen): for sub in itersubclasses(sub, _seen):
yield sub yield sub
def new_bank_statement_parser(parser_name, *args, **kwargs): def new_bank_statement_parser(parser_name, *args, **kwargs):
""" """
Return an instance of the good parser class base on the providen name Return an instance of the good parser class base on the providen name
@@ -215,4 +217,3 @@ def new_bank_statement_parser(parser_name, *args, **kwargs):
if cls.parser_for(parser_name): if cls.parser_for(parser_name):
return cls(parser_name, *args, **kwargs) return cls(parser_name, *args, **kwargs)
raise ValueError raise ValueError

View File

@@ -21,61 +21,67 @@
from openerp.tools.translate import _ from openerp.tools.translate import _
import datetime import datetime
import netsvc
logger = netsvc.Logger()
from openerp.osv.orm import Model from openerp.osv.orm import Model
from openerp.osv import fields, osv from openerp.osv import fields, osv
from parser import new_bank_statement_parser from parser import new_bank_statement_parser
import sys import sys
import traceback import traceback
class AccountStatementProfil(Model): class AccountStatementProfil(Model):
_inherit = "account.statement.profile" _inherit = "account.statement.profile"
def get_import_type_selection(self, cr, uid, context=None): def get_import_type_selection(self, cr, uid, context=None):
""" """
Has to be inherited to add parser Has to be inherited to add parser
""" """
return [('generic_csvxls_so', 'Generic .csv/.xls based on SO Name')] return [('generic_csvxls_so', 'Generic .csv/.xls based on SO Name')]
_columns = { _columns = {
'launch_import_completion': fields.boolean("Launch completion after import", 'launch_import_completion': fields.boolean(
help="Tic that box to automatically launch the completion on each imported\ "Launch completion after import",
file using this profile."), help="Tic that box to automatically launch the completion "
"on each imported file using this profile."),
'last_import_date': fields.datetime("Last Import Date"), 'last_import_date': fields.datetime("Last Import Date"),
'rec_log': fields.text('log', readonly=True), 'rec_log': fields.text('log', readonly=True),
'import_type': fields.selection(get_import_type_selection, 'Type of import', required=True, 'import_type': fields.selection(
help = "Choose here the method by which you want to import bank statement for this profile."), get_import_type_selection,
'Type of import',
required=True,
help="Choose here the method by which you want to import bank"
"statement for this profile."),
} }
def write_logs_after_import(self, cr, uid, ids, statement_id, num_lines, context): def write_logs_after_import(self, cr, uid, ids, statement_id, num_lines, context):
""" """
Write the log in the logger + in the log field of the profile to report the user about Write the log in the logger + in the log field of the profile to report the user about
what has been done. what has been done.
:param int/long statement_id: ID of the concerned account.bank.statement :param int/long statement_id: ID of the concerned account.bank.statement
:param int/long num_lines: Number of line that have been parsed :param int/long num_lines: Number of line that have been parsed
:return: True :return: True
""" """
if type(ids) is int: if isinstance(ids, (int, long)):
ids = [ids] ids = [ids]
for id in ids: for id in ids:
log = self.read(cr, uid, id, ['rec_log'], context=context)['rec_log'] log = self.read(cr, uid, id, ['rec_log'], context=context)['rec_log']
log_line = log and log.split("\n") or [] log_line = log and log.split("\n") or []
import_date = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') import_date = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
log_line[0:0] = [import_date + ' : ' log_line[0:0] = [import_date + ' : '
+ _("Bank Statement ID %s have been imported with %s lines ") %(statement_id, num_lines)] + _("Bank Statement ID %s have been imported with %s lines ") % (statement_id, num_lines)]
log = "\n".join(log_line) log = "\n".join(log_line)
self.write(cr, uid, id, {'rec_log' : log, 'last_import_date':import_date}, context=context) self.write(cr, uid, id, {'rec_log': log, 'last_import_date': import_date}, context=context)
self.message_post(cr, uid, [statement_id], body=_('Statement ID %s have been imported with %s lines.') self.message_post(
% (statement_id,num_lines), context=context) cr,
uid,
[statement_id],
body=_('Statement ID %s have been imported with %s lines.') % (statement_id, num_lines),
context=context)
return True return True
def prepare_global_commission_line_vals(self, cr, uid, parser, def prepare_global_commission_line_vals(
result_row_list, profile, statement_id, context): self, cr, uid, parser, result_row_list, profile, statement_id, context):
""" """
Prepare the global commission line if there is one. The global Prepare the global commission line if there is one. The global
commission is computed by by calling the get_st_line_commision commission is computed by by calling the get_st_line_commision
@@ -94,9 +100,8 @@ class AccountStatementProfil(Model):
partner_id = profile.partner_id and profile.partner_id.id or False 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_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 commission_analytic_id = profile.commission_analytic_id and profile.commission_analytic_id.id or False
statement_line_obj = self.pool.get('account.bank.statement.line')
comm_values = { comm_values = {
'name': 'IN '+ _('Commission line'), 'name': 'IN ' + _('Commission line'),
'date': datetime.datetime.now().date(), 'date': datetime.datetime.now().date(),
'amount': parser.get_st_line_commision(), 'amount': parser.get_st_line_commision(),
'partner_id': partner_id, 'partner_id': partner_id,
@@ -109,15 +114,16 @@ class AccountStatementProfil(Model):
'already_completed': True, 'already_completed': True,
} }
return comm_values return comm_values
def prepare_statetement_lines_vals(self, cursor, uid, parser_vals, def prepare_statetement_lines_vals(
account_payable, account_receivable, statement_id, context): self, cursor, uid, parser_vals, account_payable, account_receivable,
statement_id, context):
""" """
Hook to build the values of a line from the parser returned values. At 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 least it fullfill the statement_id and account_id. Overide it to add your
own completion if needed. own completion if needed.
:param dict of vals from parser for account.bank.statement.line (called by :param dict of vals from parser for account.bank.statement.line (called by
parser.get_st_line_vals) parser.get_st_line_vals)
:param int/long account_payable: ID of the receivable account to use :param int/long account_payable: ID of the receivable account to use
:param int/long account_receivable: ID of the payable account to use :param int/long account_receivable: ID of the payable account to use
@@ -126,7 +132,7 @@ class AccountStatementProfil(Model):
""" """
statement_obj = self.pool.get('account.bank.statement') statement_obj = self.pool.get('account.bank.statement')
values = parser_vals values = parser_vals
values['statement_id']= statement_id values['statement_id'] = statement_id
values['account_id'] = statement_obj.get_account_for_counterpart( values['account_id'] = statement_obj.get_account_for_counterpart(
cursor, cursor,
uid, uid,
@@ -135,7 +141,7 @@ class AccountStatementProfil(Model):
account_payable account_payable
) )
return values return values
def statement_import(self, cursor, uid, ids, profile_id, file_stream, ftype="csv", context=None): def statement_import(self, cursor, uid, ids, profile_id, file_stream, ftype="csv", context=None):
""" """
Create a bank statement with the given profile and parser. It will fullfill the bank statement Create a bank statement with the given profile and parser. It will fullfill the bank statement
@@ -143,13 +149,12 @@ class AccountStatementProfil(Model):
the right account). This will be done in a second step with the completion rules. 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 and record the providen file as It will also create the commission line if it apply and record the providen file as
an attachement of the bank statement. an attachement of the bank statement.
:param int/long profile_id: ID of the profile used to import the file :param int/long profile_id: ID of the profile used to import the file
:param filebuffer file_stream: binary of the providen file :param filebuffer file_stream: binary of the providen file
:param char: ftype represent the file exstension (csv by default) :param char: ftype represent the file exstension (csv by default)
:return: ID of the created account.bank.statemênt :return: ID of the created account.bank.statemênt
""" """
context = context or {}
statement_obj = self.pool.get('account.bank.statement') statement_obj = self.pool.get('account.bank.statement')
statement_line_obj = self.pool.get('account.bank.statement.line') statement_line_obj = self.pool.get('account.bank.statement.line')
attachment_obj = self.pool.get('ir.attachment') attachment_obj = self.pool.get('ir.attachment')
@@ -158,8 +163,8 @@ class AccountStatementProfil(Model):
raise osv.except_osv( raise osv.except_osv(
_("No Profile !"), _("No Profile !"),
_("You must provide a valid profile to import a bank statement !")) _("You must provide a valid profile to import a bank statement !"))
prof = prof_obj.browse(cursor,uid,profile_id,context) prof = prof_obj.browse(cursor, uid, profile_id, context=context)
parser = new_bank_statement_parser(prof.import_type, ftype=ftype) parser = new_bank_statement_parser(prof.import_type, ftype=ftype)
result_row_list = parser.parse(file_stream) result_row_list = parser.parse(file_stream)
# Check all key are present in account.bank.statement.line !! # Check all key are present in account.bank.statement.line !!
@@ -168,55 +173,60 @@ class AccountStatementProfil(Model):
if col not in statement_line_obj._columns: if col not in statement_line_obj._columns:
raise osv.except_osv( raise osv.except_osv(
_("Missing column !"), _("Missing column !"),
_("Column %s you try to import is not present in the bank statement line !") %(col)) _("Column %s you try to import is not "
"present in the bank statement line !") % col)
statement_id = statement_obj.create(cursor,uid,{'profile_id':prof.id,},context)
account_receivable, account_payable = statement_obj.get_default_pay_receiv_accounts(cursor, uid, context) statement_id = statement_obj.create(
cursor, uid, {'profile_id': prof.id}, context=context)
account_receivable, account_payable = statement_obj.get_default_pay_receiv_accounts(
cursor, uid, context)
try: try:
# Record every line in the bank statement and compute the global commission # Record every line in the bank statement and compute the global commission
# based on the commission_amount column # based on the commission_amount column
for line in result_row_list: for line in result_row_list:
parser_vals = parser.get_st_line_vals(line) parser_vals = parser.get_st_line_vals(line)
values = self.prepare_statetement_lines_vals(cursor, uid, parser_vals, account_payable, values = self.prepare_statetement_lines_vals(
account_receivable, statement_id, context) cursor, uid, parser_vals, account_payable,
account_receivable, statement_id, context)
# we finally create the line in system # we finally create the line in system
statement_line_obj.create(cursor, uid, values, context=context) statement_line_obj.create(cursor, uid, values, context=context)
# Build and create the global commission line for the whole statement # 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) comm_vals = self.prepare_global_commission_line_vals(
cursor, uid, parser, result_row_list, prof, statement_id, context)
if comm_vals: if comm_vals:
res = statement_line_obj.create(cursor, uid, comm_vals,context=context) statement_line_obj.create(cursor, uid, comm_vals, context=context)
attachment_obj.create( attachment_obj.create(
cursor, cursor,
uid, uid,
{ {
'name': 'statement file', 'name': 'statement file',
'datas': file_stream, 'datas': file_stream,
'datas_fname': "%s.%s"%(datetime.datetime.now().date(), 'datas_fname': "%s.%s" % (
ftype), datetime.datetime.now().date(),
ftype),
'res_model': 'account.bank.statement', 'res_model': 'account.bank.statement',
'res_id': statement_id, 'res_id': statement_id,
}, },
context=context context=context
) )
# If user ask to launch completion at end of import, do it ! # If user ask to launch completion at end of import, do it !
if prof.launch_import_completion: if prof.launch_import_completion:
statement_obj.button_auto_completion(cursor, uid, [statement_id], context) statement_obj.button_auto_completion(cursor, uid, [statement_id], context)
# Write the needed log infos on profile # Write the needed log infos on profile
self.write_logs_after_import(cursor, uid, prof.id, statement_id, self.write_logs_after_import(
len(result_row_list), context) cursor, uid, prof.id, statement_id, len(result_row_list), context)
except Exception, exc: except Exception:
statement_obj.unlink(cursor, uid, [statement_id]) statement_obj.unlink(cursor, uid, [statement_id], context=context)
error_type, error_value, trbk = sys.exc_info() error_type, error_value, trbk = sys.exc_info()
st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value) st = "Error: %s\nDescription: %s\nTraceback:" % (error_type.__name__, error_value)
st += ''.join(traceback.format_tb(trbk, 30)) st += ''.join(traceback.format_tb(trbk, 30))
raise osv.except_osv( raise osv.except_osv(
_("Statement import error"), _("Statement import error"),
_("The statement cannot be created : %s") %(st)) _("The statement cannot be created : %s") % st)
return statement_id return statement_id
class AccountStatementLine(Model): class AccountStatementLine(Model):
@@ -227,8 +237,9 @@ class AccountStatementLine(Model):
""" """
_inherit = "account.bank.statement.line" _inherit = "account.bank.statement.line"
_columns={ _columns = {
'commission_amount': fields.sparse(type='float', string='Line Commission Amount', 'commission_amount': fields.sparse(
type='float',
string='Line Commission Amount',
serialization_field='additionnal_bank_fields'), serialization_field='additionnal_bank_fields'),
} }

View File

@@ -23,17 +23,18 @@
Wizard to import financial institute date in bank statement Wizard to import financial institute date in bank statement
""" """
from openerp.osv import osv, orm from openerp.osv import orm, fields
from openerp.osv import fields, osv
from openerp.tools.translate import _ from openerp.tools.translate import _
import os import os
class CreditPartnerStatementImporter(orm.TransientModel): class CreditPartnerStatementImporter(orm.TransientModel):
_name = "credit.statement.import" _name = "credit.statement.import"
def default_get(self, cr, uid, fields, context=None): def default_get(self, cr, uid, fields, context=None):
if context is None: context = {} if context is None:
context = {}
res = {} res = {}
if (context.get('active_model', False) == 'account.statement.profile' and if (context.get('active_model', False) == 'account.statement.profile' and
context.get('active_ids', False)): context.get('active_ids', False)):
@@ -41,55 +42,56 @@ class CreditPartnerStatementImporter(orm.TransientModel):
assert len(ids) == 1, 'You cannot use this on more than one profile !' assert len(ids) == 1, 'You cannot use this on more than one profile !'
res['profile_id'] = ids[0] res['profile_id'] = ids[0]
other_vals = self.onchange_profile_id(cr, uid, [], res['profile_id'], context=context) other_vals = self.onchange_profile_id(cr, uid, [], res['profile_id'], context=context)
res.update(other_vals.get('value',{})) res.update(other_vals.get('value', {}))
return res return res
_columns = { _columns = {
'profile_id': fields.many2one('account.statement.profile', 'profile_id': fields.many2one('account.statement.profile',
'Import configuration parameter', 'Import configuration parameter',
required=True), required=True),
'input_statement': fields.binary('Statement file', required=True), 'input_statement': fields.binary('Statement file', required=True),
'partner_id': fields.many2one('res.partner', 'partner_id': fields.many2one('res.partner',
'Credit insitute partner', 'Credit insitute partner'),
),
'journal_id': fields.many2one('account.journal', 'journal_id': fields.many2one('account.journal',
'Financial journal to use transaction', 'Financial journal to use transaction'),
),
'input_statement': fields.binary('Statement file', required=True),
'file_name': fields.char('File Name', size=128), 'file_name': fields.char('File Name', size=128),
'commission_account_id': fields.many2one('account.account', 'commission_account_id': fields.many2one('account.account',
'Commission account', 'Commission account'),
),
'commission_analytic_id': fields.many2one('account.analytic.account', 'commission_analytic_id': fields.many2one('account.analytic.account',
'Commission analytic account', 'Commission analytic account'),
),
'receivable_account_id': fields.many2one('account.account', 'receivable_account_id': fields.many2one('account.account',
'Force Receivable/Payable Account'), 'Force Receivable/Payable Account'),
'force_partner_on_bank': fields.boolean('Force partner on bank move', 'force_partner_on_bank': fields.boolean(
help="Tic that box if you want to use the credit insitute partner\ 'Force partner on bank move',
in the counterpart of the treasury/banking 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', 'balance_check': fields.boolean(
help="Tic that box if you want OpenERP to control the start/end balance\ 'Balance check',
before confirming a bank statement. If don't ticked, no balance control will be done." 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."),
}
def onchange_profile_id(self, cr, uid, ids, profile_id, context=None): def onchange_profile_id(self, cr, uid, ids, profile_id, context=None):
res={} res = {}
if profile_id: if profile_id:
c = self.pool.get("account.statement.profile").browse(cr,uid,profile_id) c = self.pool.get("account.statement.profile").browse(
res = {'value': {'partner_id': c.partner_id and c.partner_id.id or False, cr, uid, profile_id, context=context)
'journal_id': c.journal_id and c.journal_id.id or False, 'commission_account_id': \ res = {'value':
c.commission_account_id and c.commission_account_id.id or False, {'partner_id': c.partner_id and c.partner_id.id or False,
'receivable_account_id': c.receivable_account_id and c.receivable_account_id.id or False, 'journal_id': c.journal_id and c.journal_id.id or False,
'commission_a':c.commission_analytic_id and c.commission_analytic_id.id or False, 'commission_account_id':
'force_partner_on_bank':c.force_partner_on_bank, c.commission_account_id and c.commission_account_id.id or False,
'balance_check':c.balance_check,}} '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 return res
def _check_extension(self, filename): def _check_extension(self, filename):
(shortname, ftype) = os.path.splitext(filename) (__, ftype) = os.path.splitext(filename)
if not ftype: if not ftype:
#We do not use osv exception we do not want to have it logged #We do not use osv exception we do not want to have it logged
raise Exception(_('Please use a file with an extention')) raise Exception(_('Please use a file with an extention'))
@@ -109,7 +111,7 @@ class CreditPartnerStatementImporter(orm.TransientModel):
False, False,
importer.profile_id.id, importer.profile_id.id,
importer.input_statement, importer.input_statement,
ftype.replace('.',''), ftype.replace('.', ''),
context=context context=context
) )
model_obj = self.pool.get('ir.model.data') model_obj = self.pool.get('ir.model.data')

View File

@@ -18,4 +18,3 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
############################################################################## ##############################################################################

View File

@@ -24,8 +24,11 @@
'author': 'Camptocamp', 'author': 'Camptocamp',
'maintainer': 'Camptocamp', 'maintainer': 'Camptocamp',
'category': 'Finance', 'category': 'Finance',
'complexity': 'normal', #easy, normal, expert 'complexity': 'normal',
'depends': ['account_statement_base_completion','account_voucher'], 'depends': [
'account_statement_base_completion',
'account_voucher'
],
'description': """ 'description': """
This module is only needed when using account_statement_base_completion with voucher in order adapt the view correctly. This module is only needed when using account_statement_base_completion with voucher in order adapt the view correctly.
""", """,

View File

@@ -21,4 +21,4 @@
import statement import statement
import report import report
import account import account

View File

@@ -24,8 +24,11 @@
'author': 'Camptocamp', 'author': 'Camptocamp',
'maintainer': 'Camptocamp', 'maintainer': 'Camptocamp',
'category': 'Finance', 'category': 'Finance',
'complexity': 'normal', #easy, normal, expert 'complexity': 'normal',
'depends': ['account','report_webkit'], 'depends': [
'account',
'report_webkit'
],
'description': """ 'description': """
Improve the basic bank statement, by adding various new features, Improve the basic bank statement, by adding various new features,
and help dealing with huge volume of reconciliation through payment offices such as Paypal, Lazer, and help dealing with huge volume of reconciliation through payment offices such as Paypal, Lazer,

View File

@@ -20,12 +20,12 @@
############################################################################## ##############################################################################
from openerp.osv.orm import Model from openerp.osv.orm import Model
from openerp.osv import fields, osv from openerp.osv import fields
class account_move(Model): class account_move(Model):
_inherit='account.move' _inherit = 'account.move'
def unlink(self, cr, uid, ids, context=None): def unlink(self, cr, uid, ids, context=None):
""" """
Delete the reconciliation when we delete the moves. This Delete the reconciliation when we delete the moves. This
@@ -36,6 +36,3 @@ class account_move(Model):
if move_line.reconcile_id: if move_line.reconcile_id:
move_line.reconcile_id.unlink(context=context) move_line.reconcile_id.unlink(context=context)
return super(account_move, self).unlink(cr, uid, ids, context=context) return super(account_move, self).unlink(cr, uid, ids, context=context)

View File

@@ -1,9 +1,4 @@
# -*- encoding: utf-8 -*- # -*- coding: utf-8 -*-
#
# __init__.py
#
# Copyright (c) 2009 CamptoCamp. All rights reserved.
##############################################################################
# #
# WARNING: This program as such is intended to be used by professional # WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential # programmers who take the whole responsability of assessing all potential
@@ -28,4 +23,4 @@
# #
############################################################################## ##############################################################################
import bank_statement_report import bank_statement_report

View File

@@ -1,4 +1,4 @@
# -*- encoding: utf-8 -*- # -*- coding utf-8 -*-
############################################################################## ##############################################################################
# #
# Author: Nicolas Bessi. Copyright Camptocamp SA # Author: Nicolas Bessi. Copyright Camptocamp SA
@@ -18,16 +18,13 @@
# #
############################################################################## ##############################################################################
import time from openerp.report import report_sxw
from report import report_sxw
from openerp.tools.translate import _ from openerp.tools.translate import _
import pooler from openerp import pooler
from operator import add, itemgetter
from itertools import groupby
from datetime import datetime from datetime import datetime
from report_webkit import webkit_report from report_webkit import webkit_report
class BankStatementWebkit(report_sxw.rml_parse): class BankStatementWebkit(report_sxw.rml_parse):
def __init__(self, cursor, uid, name, context): def __init__(self, cursor, uid, name, context):
@@ -35,15 +32,15 @@ class BankStatementWebkit(report_sxw.rml_parse):
self.pool = pooler.get_pool(self.cr.dbname) self.pool = pooler.get_pool(self.cr.dbname)
self.cursor = self.cr self.cursor = self.cr
company = self.pool.get('res.users').browse(self.cr, uid, uid, context=context).company_id company = self.pool.get('res.users').browse(
self.cr, uid, uid, context=context).company_id
header_report_name = ' - '.join((_('BORDEREAU DE REMISE DE CHEQUES'), header_report_name = ' - '.join((_('BORDEREAU DE REMISE DE CHEQUES'),
company.name, company.currency_id.name)) company.name, company.currency_id.name))
statement = self.pool.get('account.bank.statement').browse(cursor,uid,context['active_id']);
footer_date_time = self.formatLang(str(datetime.today())[:19], date_time=True) footer_date_time = self.formatLang(str(datetime.today())[:19], date_time=True)
self.localcontext.update({ self.localcontext.update({
'cr': cursor, 'cr': cursor,
'uid': uid, 'uid': uid,
'get_bank_statement' : self._get_bank_statement_data, 'get_bank_statement': self._get_bank_statement_data,
'report_name': _('BORDEREAU DE REMISE DE CHEQUES'), 'report_name': _('BORDEREAU DE REMISE DE CHEQUES'),
'additional_args': [ 'additional_args': [
('--header-font-name', 'Helvetica'), ('--header-font-name', 'Helvetica'),
@@ -57,10 +54,15 @@ class BankStatementWebkit(report_sxw.rml_parse):
('--footer-line',), ('--footer-line',),
], ],
}) })
def _get_bank_statement_data(self,statement):
def _get_bank_statement_data(self, statement):
statement_obj = self.pool.get('account.bank.statement.line') statement_obj = self.pool.get('account.bank.statement.line')
statement_line_ids = statement_obj.search(self.cr,self.uid,[['statement_id','=',statement.id]]) statement_line_ids = statement_obj.search(
statement_lines = statement_obj.browse(self.cr,self.uid,statement_line_ids) self.cr,
self.uid,
[('statement_id', '=', statement.id)])
statement_lines = statement_obj.browse(
self.cr, self.uid, statement_line_ids)
return statement_lines return statement_lines
webkit_report.WebKitParser('report.bank_statement_webkit', webkit_report.WebKitParser('report.bank_statement_webkit',

View File

@@ -19,11 +19,10 @@
# #
############################################################################## ##############################################################################
import time
from openerp.osv.orm import Model from openerp.osv.orm import Model
from openerp.osv import fields, osv, osv from openerp.osv import fields, osv
from openerp.tools.translate import _ from openerp.tools.translate import _
import openerp.addons.decimal_precision as dp
class AccountStatementProfil(Model): class AccountStatementProfil(Model):
""" """
@@ -33,45 +32,53 @@ class AccountStatementProfil(Model):
""" """
_name = "account.statement.profile" _name = "account.statement.profile"
_description = "Statement Profil" _description = "Statement Profil"
_columns = { _columns = {
'name': fields.char('Name', size=128, required=True), 'name': fields.char('Name', required=True),
'partner_id': fields.many2one('res.partner', 'partner_id': fields.many2one(
'Bank/Payment Office partner', 'res.partner',
help="Put a partner if you want to have it on the commission move \ 'Bank/Payment Office partner',
(and optionaly on the counterpart of the intermediate/banking move \ help="Put a partner if you want to have it on the "
if you tic the corresponding checkbox)."), "commission move (and optionaly on the counterpart "
'journal_id': fields.many2one('account.journal', "of the intermediate/banking move if you tick the "
'Financial journal to use for transaction', "corresponding checkbox)."),
required=True), 'journal_id': fields.many2one(
'commission_account_id': fields.many2one('account.account', 'account.journal',
'Commission account', 'Financial journal to use for transaction',
required=True), required=True),
'commission_analytic_id': fields.many2one('account.analytic.account', 'commission_account_id': fields.many2one(
'Commission analytic account'), 'account.account',
'receivable_account_id': fields.many2one('account.account', 'Commission account',
'Force Receivable/Payable Account', required=True),
help="Choose a receivable account to force the default\ 'commission_analytic_id': fields.many2one(
debit/credit account (eg. an intermediat bank account instead of\ 'account.analytic.account',
default debitors)."), 'Commission analytic account'),
'force_partner_on_bank': fields.boolean('Force partner on bank move', 'receivable_account_id': fields.many2one(
help="Tic that box if you want to use the credit insitute partner\ 'account.account',
in the counterpart of the intermediat/banking move." 'Force Receivable/Payable Account',
), help="Choose a receivable account to force the default "
'balance_check': fields.boolean('Balance check', "debit/credit account (eg. an intermediat bank account "
help="Tic that box if you want OpenERP to control the start/end \ "instead of default debitors)."),
balance before confirming a bank statement. If don't ticked, no \ 'force_partner_on_bank': fields.boolean(
balance control will be done." 'Force partner on bank move',
), help="Tick that box if you want to use the credit "
'bank_statement_prefix': fields.char('Bank Statement Prefix', size=32), "institute partner in the counterpart of the "
'bank_statement_ids': fields.one2many('account.bank.statement', 'profile_id', 'Bank Statement Imported'), "intermediate/banking move."),
'balance_check': fields.boolean(
'Balance check',
help="Tick 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."
),
'bank_statement_prefix': fields.char(
'Bank Statement Prefix', size=32),
'bank_statement_ids': fields.one2many(
'account.bank.statement', 'profile_id', 'Bank Statement Imported'),
} }
def _check_partner(self, cr, uid, ids, context=None): def _check_partner(self, cr, uid, ids, context=None):
obj = self.browse(cr, uid, ids[0], context=context) obj = self.browse(cr, uid, ids[0], context=context)
if obj.partner_id == False and obj.force_partner_on_bank: if obj.partner_id is False and obj.force_partner_on_bank:
return False return False
return True return True
@@ -82,57 +89,65 @@ class AccountStatementProfil(Model):
class AccountBankSatement(Model): class AccountBankSatement(Model):
""" """
We improve the bank statement class mostly for : We improve the bank statement class mostly for :
- Removing the period and compute it from the date of each line. - Removing the period and compute it from the date of each line.
- Allow to remove the balance check depending on the chosen profile - Allow to remove the balance check depending on the chosen profile
- Report errors on confirmation all at once instead of crashing onr by one - Report errors on confirmation all at once instead of crashing onr by one
- Add a profile notion that can change the generated entries on statement - Add a profile notion that can change the generated entries on statement
confirmation. confirmation.
For this, we had to override quite some long method and we'll need to maintain For this, we had to override quite some long method and we'll need to maintain
them up to date. Changes are point up by '#Chg' comment. them up to date. Changes are point up by '#Chg' comment.
""" """
_inherit = "account.bank.statement" _inherit = "account.bank.statement"
_columns = { _columns = {
'profile_id': fields.many2one('account.statement.profile', 'profile_id': fields.many2one(
'Profil', required=True, readonly=True, states={'draft': [('readonly', False)]}), 'account.statement.profile',
'Profil',
required=True,
readonly=True,
states={'draft': [('readonly', False)]}),
'credit_partner_id': fields.related( 'credit_partner_id': fields.related(
'profile_id', 'profile_id',
'partner_id', 'partner_id',
type='many2one',
relation='res.partner',
string='Financial Partner',
store=True, readonly=True),
'balance_check': fields.related(
'profile_id',
'balance_check',
type='boolean',
string='Balance check',
store=True, readonly=True),
'journal_id': fields.related(
'profile_id',
'journal_id',
type='many2one', type='many2one',
relation='account.journal', relation='res.partner',
string='Journal', string='Financial Partner',
store=True, readonly=True), store=True,
'period_id': fields.many2one('account.period', 'Period', required=False, readonly=True), readonly=True),
'balance_check': fields.related(
'profile_id',
'balance_check',
type='boolean',
string='Balance check',
store=True,
readonly=True),
'journal_id': fields.related(
'profile_id',
'journal_id',
type='many2one',
relation='account.journal',
string='Journal',
store=True,
readonly=True),
'period_id': fields.many2one(
'account.period', 'Period', required=False, readonly=True),
} }
_defaults = { _defaults = {
'period_id': lambda *a: False, 'period_id': False,
} }
def create(self, cr, uid, vals, context=None): def create(self, cr, uid, vals, context=None):
"""Need to pass the journal_id in vals anytime because of account.cash.statement """Need to pass the journal_id in vals anytime because of account.cash.statement
need it.""" need it."""
if 'profile_id' in vals: if 'profile_id' in vals:
profile_obj = self.pool.get('account.statement.profile') profile_obj = self.pool.get('account.statement.profile')
profile = profile_obj.browse(cr,uid,vals['profile_id'],context) profile = profile_obj.browse(cr, uid, vals['profile_id'], context=context)
vals['journal_id'] = profile.journal_id.id vals['journal_id'] = profile.journal_id.id
return super(AccountBankSatement, self).create(cr, uid, vals, context=context) return super(AccountBankSatement, self).create(cr, uid, vals, context=context)
def _get_period(self, cursor, uid, date, context=None): def _get_period(self, cursor, uid, date, context=None):
""" """
Find matching period for date, used in the statement line creation. Find matching period for date, used in the statement line creation.
@@ -140,7 +155,7 @@ class AccountBankSatement(Model):
period_obj = self.pool.get('account.period') period_obj = self.pool.get('account.period')
periods = period_obj.find(cursor, uid, dt=date, context=context) periods = period_obj.find(cursor, uid, dt=date, context=context)
return periods and periods[0] or False return periods and periods[0] or False
def _check_company_id(self, cr, uid, ids, context=None): def _check_company_id(self, cr, uid, ids, context=None):
""" """
Adapt this constraint method from the account module to reflect the Adapt this constraint method from the account module to reflect the
@@ -157,25 +172,27 @@ class AccountBankSatement(Model):
return True return True
_constraints = [ _constraints = [
(_check_company_id, 'The journal and period chosen have to belong to the same company.', ['journal_id','period_id']), (_check_company_id,
'The journal and period chosen have to belong to the same company.',
['journal_id', 'period_id']),
] ]
def button_cancel(self, cr, uid, ids, context={}): def button_cancel(self, cr, uid, ids, context=None):
""" """
We cancel the related move, delete them and finally put the We cancel the related move, delete them and finally put the
statement in draft state. So no need to unreconcile all entries, statement in draft state. So no need to unreconcile all entries,
then unpost them, then finaly cancel the bank statement. then unpost them, then finaly cancel the bank statement.
""" """
done = []
for st in self.browse(cr, uid, ids, context=context): for st in self.browse(cr, uid, ids, context=context):
if st.state=='draft': if st.state == 'draft':
continue continue
ids = [] ids = []
for line in st.line_ids: for line in st.line_ids:
for move in line.move_ids: for move in line.move_ids:
if move.state <> 'draft': if move.state != 'draft':
move.button_cancel(context=context) move.button_cancel(context=context)
return super(AccountBankSatement, self).button_cancel(cr, uid, vals, context=context) return super(AccountBankSatement, self).button_cancel(
cr, uid, ids, context=context)
def _prepare_move(self, cr, uid, st_line, st_line_number, context=None): def _prepare_move(self, cr, uid, st_line, st_line_number, context=None):
"""Add the period_id from the statement line date to the move preparation. """Add the period_id from the statement line date to the move preparation.
@@ -187,19 +204,21 @@ class AccountBankSatement(Model):
""" """
if context is None: if context is None:
context = {} context = {}
res = super(AccountBankSatement, self)._prepare_move(cr, uid, st_line, st_line_number, context=context) res = super(AccountBankSatement, self)._prepare_move(
cr, uid, st_line, st_line_number, context=context)
ctx = context.copy() ctx = context.copy()
ctx['company_id'] = st_line.company_id.id ctx['company_id'] = st_line.company_id.id
period_id = self._get_period(cr, uid, st_line.date, context=ctx) period_id = self._get_period(cr, uid, st_line.date, context=ctx)
res.update({'period_id': period_id}) res.update({'period_id': period_id})
return res return res
def _prepare_move_line_vals(self, cr, uid, st_line, move_id, debit, credit, currency_id = False, def _prepare_move_line_vals(
amount_currency= False, account_id = False, analytic_id = False, self, cr, uid, st_line, move_id, debit, credit, currency_id=False,
partner_id = False, context=None): amount_currency=False, account_id=False, analytic_id=False,
partner_id=False, context=None):
"""Add the period_id from the statement line date to the move preparation. """Add the period_id from the statement line date to the move preparation.
Originaly, it was taken from the statement period_id Originaly, it was taken from the statement period_id
:param browse_record st_line: account.bank.statement.line record to :param browse_record st_line: account.bank.statement.line record to
create the move from. create the move from.
:param int/long move_id: ID of the account.move to link the move line :param int/long move_id: ID of the account.move to link the move line
@@ -215,9 +234,13 @@ class AccountBankSatement(Model):
""" """
if context is None: if context is None:
context = {} context = {}
res = super(AccountBankSatement, self)._prepare_move_line_vals(cr, uid, st_line, move_id, debit, res = super(AccountBankSatement, self)._prepare_move_line_vals(
credit, currency_id = currency_id, amount_currency = amount_currency, account_id = account_id, cr, uid, st_line, move_id, debit, credit,
analytic_id = analytic_id, partner_id = partner_id, context = context) currency_id=currency_id,
amount_currency=amount_currency,
account_id=account_id,
analytic_id=analytic_id,
partner_id=partner_id, context=context)
ctx = context.copy() ctx = context.copy()
ctx['company_id'] = st_line.company_id.id ctx['company_id'] = st_line.company_id.id
period_id = self._get_period(cr, uid, st_line.date, context=ctx) period_id = self._get_period(cr, uid, st_line.date, context=ctx)
@@ -235,28 +258,31 @@ class AccountBankSatement(Model):
create the move from. create the move from.
:return: int/long of the res.partner to use as counterpart :return: int/long of the res.partner to use as counterpart
""" """
bank_partner_id = super(AccountBankSatement, self)._get_counter_part_partner(cr, uid, st_line, context=context) bank_partner_id = super(AccountBankSatement, self).\
# GET THE RIGHT PARTNER ACCORDING TO THE CHOSEN PROFIL _get_counter_part_partner(cr, uid, st_line, context=context)
# get the right partner according to the chosen profil
if st_line.statement_id.profile_id.force_partner_on_bank: if st_line.statement_id.profile_id.force_partner_on_bank:
bank_partner_id = st_line.statement_id.profile_id.partner_id.id bank_partner_id = st_line.statement_id.profile_id.partner_id.id
return bank_partner_id return bank_partner_id
def _get_st_number_period_profile(self, cr, uid, date, profile_id): def _get_st_number_period_profile(self, cr, uid, date, profile_id):
""" """
Retrieve the name of bank statement from sequence, according to the period Retrieve the name of bank statement from sequence, according to the period
corresponding to the date passed in args. Add a prefix if set in the profile. corresponding to the date passed in args. Add a prefix if set in the profile.
:param: date: date of the statement used to compute the right period :param: date: date of the statement used to compute the right period
:param: int/long: profile_id: the account.statement.profile ID from which to take the :param: int/long: profile_id: the account.statement.profile ID from which to take the
bank_statement_prefix for the name bank_statement_prefix for the name
:return: char: name of the bank statement (st_number) :return: char: name of the bank statement (st_number)
""" """
year = self.pool.get('account.period').browse(cr, uid, self._get_period(cr, uid, date)).fiscalyear_id.id year = self.pool.get('account.period').browse(
profile = self.pool.get('account.statement.profile').browse(cr,uid, profile_id) cr, uid, self._get_period(cr, uid, date)).fiscalyear_id.id
profile = self.pool.get('account.statement.profile').browse(cr, uid, profile_id)
c = {'fiscalyear_id': year} c = {'fiscalyear_id': year}
obj_seq = self.pool.get('ir.sequence') obj_seq = self.pool.get('ir.sequence')
journal_sequence_id = profile.journal_id.sequence_id and profile.journal_id.sequence_id.id or False journal_sequence_id = (profile.journal_id.sequence_id and
profile.journal_id.sequence_id.id or False)
if journal_sequence_id: if journal_sequence_id:
st_number = obj_seq.next_by_id(cr, uid, journal_sequence_id, context=c) st_number = obj_seq.next_by_id(cr, uid, journal_sequence_id, context=c)
else: else:
@@ -272,19 +298,16 @@ class AccountBankSatement(Model):
instead of having them pop one by one. instead of having them pop one by one.
We have to copy paste a big block of code, changing the error We have to copy paste a big block of code, changing the error
stack + managing period from date. stack + managing period from date.
TODO: Log the error in a bank statement field instead of using a popup ! TODO: Log the error in a bank statement field instead of using a popup !
""" """
# obj_seq = self.pool.get('irerrors_stack.sequence')
if context is None:
context = {}
for st in self.browse(cr, uid, ids, context=context): for st in self.browse(cr, uid, ids, context=context):
j_type = st.journal_id.type j_type = st.journal_id.type
company_currency_id = st.journal_id.company_id.currency_id.id company_currency_id = st.journal_id.company_id.currency_id.id
if not self.check_status_condition(cr, uid, st.state, journal_type=j_type): if not self.check_status_condition(cr, uid, st.state, journal_type=j_type):
continue continue
self.balance_check(cr, uid, st.id, journal_type=j_type, context=context) self.balance_check(cr, uid, st.id, journal_type=j_type, context=context)
if (not st.journal_id.default_credit_account_id) \ if (not st.journal_id.default_credit_account_id) \
or (not st.journal_id.default_debit_account_id): or (not st.journal_id.default_debit_account_id):
@@ -294,11 +317,11 @@ class AccountBankSatement(Model):
if not st.name == '/': if not st.name == '/':
st_number = st.name st_number = st.name
else: else:
# Begin Changes # Begin Changes
st_number = self._get_st_number_period_profile(cr, uid, st.date, st.profile_id.id) st_number = self._get_st_number_period_profile(cr, uid, st.date, st.profile_id.id)
# End Changes # End Changes
for line in st.move_line_ids: for line in st.move_line_ids:
if line.state <> 'valid': if line.state != 'valid':
raise osv.except_osv(_('Error !'), raise osv.except_osv(_('Error !'),
_('The account entries lines are not in valid state.')) _('The account entries lines are not in valid state.'))
# begin changes # begin changes
@@ -328,18 +351,18 @@ class AccountBankSatement(Model):
'balance_end_real': st.balance_end 'balance_end_real': st.balance_end
}, context=context) }, context=context)
self.message_post(cr, uid, [st.id], body=_('Statement %s confirmed, journal items were created.') % (st_number,), context=context) self.message_post(cr, uid, [st.id], body=_('Statement %s confirmed, journal items were created.') % (st_number,), context=context)
return self.write(cr, uid, ids, {'state':'confirm'}, context=context) return self.write(cr, uid, ids, {'state': 'confirm'}, context=context)
def get_account_for_counterpart(self, cursor, uid, def get_account_for_counterpart(
amount, account_receivable, account_payable): self, cursor, uid, amount, account_receivable, account_payable):
""" """
Give the amount, payable and receivable account (that can be found using Give the amount, payable and receivable account (that can be found using
get_default_pay_receiv_accounts method) and receive the one to use. This method get_default_pay_receiv_accounts method) and receive the one to use. This method
should be use when there is no other way to know which one to take. should be use when there is no other way to know which one to take.
:param float: amount of the line :param float: amount of the line
:param int/long: account_receivable the receivable account :param int/long: account_receivable the receivable account
:param int/long: account_payable the payable account :param int/long: account_payable the payable account
:return: int/long :the default account to be used by statement line as the counterpart :return: int/long :the default account to be used by statement line as the counterpart
of the journal account depending on the amount. of the journal account depending on the amount.
""" """
@@ -358,16 +381,15 @@ class AccountBankSatement(Model):
def get_default_pay_receiv_accounts(self, cursor, uid, 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 We try to determine default payable/receivable accounts to be used as counterpart
from the company default propoerty. This is to be used if there is no otherway to from the company default propoerty. This is to be used if there is no otherway to
find the good one, or to find a default value that will be overriden by a completion find the good one, or to find a default value that will be overriden by a completion
method (rules of account_statement_base_completion) afterwards. method (rules of account_statement_base_completion) afterwards.
:return: tuple of int/long ID that give account_receivable, account_payable based on :return: tuple of int/long ID that give account_receivable, account_payable based on
company default. company default.
""" """
account_receivable = False account_receivable = False
account_payable = False account_payable = False
context = context or {}
property_obj = self.pool.get('ir.property') property_obj = self.pool.get('ir.property')
model_fields_obj = self.pool.get('ir.model.fields') model_fields_obj = self.pool.get('ir.model.fields')
model_fields_ids = model_fields_obj.search( model_fields_ids = model_fields_obj.search(
@@ -375,20 +397,20 @@ class AccountBankSatement(Model):
uid, uid,
[('name', 'in', ['property_account_receivable', [('name', 'in', ['property_account_receivable',
'property_account_payable']), 'property_account_payable']),
('model', '=', 'res.partner'),], ('model', '=', 'res.partner')],
context=context context=context
) )
property_ids = property_obj.search( property_ids = property_obj.search(
cursor, cursor,
uid, [ uid,
('fields_id', 'in', model_fields_ids), [('fields_id', 'in', model_fields_ids),
('res_id', '=', False), ('res_id', '=', False),
], ],
context=context context=context
) )
for erp_property in property_obj.browse(cursor, uid, for erp_property in property_obj.browse(
property_ids, context=context): cursor, uid, property_ids, context=context):
if erp_property.fields_id.name == 'property_account_receivable': if erp_property.fields_id.name == 'property_account_receivable':
account_receivable = erp_property.value_reference.id account_receivable = erp_property.value_reference.id
elif erp_property.fields_id.name == 'property_account_payable': elif erp_property.fields_id.name == 'property_account_payable':
@@ -399,41 +421,46 @@ class AccountBankSatement(Model):
""" """
Balance check depends on the profile. If no check for this profile is required, Balance check depends on the profile. If no check for this profile is required,
return True and do nothing, otherwise call super. return True and do nothing, otherwise call super.
:param int/long st_id: ID of the concerned account.bank.statement :param int/long st_id: ID of the concerned account.bank.statement
:param char: journal_type that concern the bank statement :param char: journal_type that concern the bank statement
:return: True :return: True
""" """
st = self.browse(cr, uid, st_id, context=context) st = self.browse(cr, uid, st_id, context=context)
if st.balance_check: if st.balance_check:
return super(AccountBankSatement,self).balance_check(cr, uid, st_id, journal_type, context) return super(AccountBankSatement, self).balance_check(
cr, uid, st_id, journal_type, context=context)
else: else:
return True return True
def onchange_imp_config_id(self, cr, uid, ids, profile_id, context=None): def onchange_imp_config_id(self, cr, uid, ids, profile_id, context=None):
""" """
Compute values on the change of the profile. Compute values on the change of the profile.
:param: int/long: profile_id that changed :param: int/long: profile_id that changed
:return dict of dict with key = name of the field :return dict of dict with key = name of the field
""" """
if not profile_id: if not profile_id:
return {} return {}
import_config = self.pool.get("account.statement.profile").browse(cr,uid,profile_id) import_config = self.pool.get("account.statement.profile").browse(
cr, uid, profile_id, context=context)
journal_id = import_config.journal_id.id journal_id = import_config.journal_id.id
account_id = import_config.journal_id.default_debit_account_id.id account_id = import_config.journal_id.default_debit_account_id.id
credit_partner_id = import_config.partner_id and import_config.partner_id.id or False credit_partner_id = import_config.partner_id and import_config.partner_id.id or False
return {'value': {'journal_id':journal_id, 'account_id': account_id, return {'value':
'balance_check':import_config.balance_check, {'journal_id': journal_id,
'credit_partner_id':credit_partner_id, 'account_id': account_id,
}} 'balance_check': import_config.balance_check,
'credit_partner_id': credit_partner_id,
}
}
class AccountBankSatementLine(Model): class AccountBankSatementLine(Model):
""" """
Override to compute the period from the date of the line, add a method to retrieve Override to compute the period from the date of the line, add a method to retrieve
the values for a line from the profile. Override the on_change method to take care of the values for a line from the profile. Override the on_change method to take care of
the profile when fullfilling the bank statement manually. Set the reference to 64 the profile when fullfilling the bank statement manually. Set the reference to 64
Char long instead 32. Char long instead 32.
""" """
_inherit = "account.bank.statement.line" _inherit = "account.bank.statement.line"
@@ -447,8 +474,8 @@ class AccountBankSatementLine(Model):
return periods and periods[0] or False return periods and periods[0] or False
def _get_default_account(self, cursor, user, context=None): def _get_default_account(self, cursor, user, context=None):
return self.get_values_for_line(cursor, user, context = context)['account_id'] return self.get_values_for_line(cursor, user, context=context)['account_id']
_columns = { _columns = {
# Set them as required + 64 char instead of 32 # Set them as required + 64 char instead of 32
'ref': fields.char('Reference', size=64, required=True), 'ref': fields.char('Reference', size=64, required=True),
@@ -458,8 +485,8 @@ class AccountBankSatementLine(Model):
'period_id': _get_period, 'period_id': _get_period,
'account_id': _get_default_account, 'account_id': _get_default_account,
} }
def get_values_for_line(self, cr, uid, profile_id = False, partner_id = False, line_type = False, amount = False, context = None): 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: 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 profile, return this value and type = general - If a receivable_account_id is set in the profile, return this value and type = general
@@ -470,7 +497,7 @@ class AccountBankSatementLine(Model):
so it is easier for the accountant to know why the receivable/payable has been chosen 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 - 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. for account_id. Note that in that case, we return the receivable one.
:param int/long profile_id of the related bank statement :param int/long profile_id of the related bank statement
:param int/long partner_id of the line :param int/long partner_id of the line
:param char line_type: a value from: 'general', 'supplier', 'customer' :param char line_type: a value from: 'general', 'supplier', 'customer'
@@ -483,17 +510,16 @@ class AccountBankSatementLine(Model):
... ...
} }
""" """
if context is None:
context = {}
res = {} res = {}
obj_partner = self.pool.get('res.partner') obj_partner = self.pool.get('res.partner')
obj_stat = self.pool.get('account.bank.statement') obj_stat = self.pool.get('account.bank.statement')
receiv_account = pay_account = account_id = False receiv_account = pay_account = account_id = False
# If profile has a receivable_account_id, we return it in any case # If profile has a receivable_account_id, we return it in any case
if profile_id: if profile_id:
profile = self.pool.get("account.statement.profile").browse(cr,uid,profile_id) profile = self.pool.get("account.statement.profile").browse(
cr, uid, profile_id, context=context)
if profile.receivable_account_id: if profile.receivable_account_id:
res['account_id'] = profile.receivable_account_id.id res['account_id'] = profile.receivable_account_id.id
res['type'] = 'general' res['type'] = 'general'
return res return res
# If partner -> take from him # If partner -> take from him
@@ -503,7 +529,8 @@ class AccountBankSatementLine(Model):
receiv_account = part.property_account_receivable.id receiv_account = part.property_account_receivable.id
# If no value, look on the default company property # If no value, look on the default company property
if not pay_account or not receiv_account: if not pay_account or not receiv_account:
receiv_account, pay_account = obj_stat.get_default_pay_receiv_accounts(cr, uid, context=None) 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 # Now we have both pay and receive account, choose the one to use
# based on line_type first, then amount, otherwise take receivable one. # based on line_type first, then amount, otherwise take receivable one.
if line_type is not False: if line_type is not False:
@@ -519,18 +546,15 @@ class AccountBankSatementLine(Model):
res['account_id'] = pay_account res['account_id'] = pay_account
res['type'] = 'supplier' res['type'] = 'supplier'
if not account_id: if not account_id:
res['account_id'] = receiv_account res['account_id'] = receiv_account
return res return res
def onchange_partner_id(self, cr, uid, ids, partner_id, profile_id, context=None): def onchange_partner_id(self, cr, uid, ids, partner_id, profile_id, context=None):
""" """
Override of the basic method as we need to pass the profile_id in the on_change_type Override of the basic method as we need to pass the profile_id in the on_change_type
call. call.
""" """
obj_partner = self.pool.get('res.partner') obj_partner = self.pool.get('res.partner')
if context is None:
context = {}
if not partner_id: if not partner_id:
return {} return {}
part = obj_partner.browse(cr, uid, partner_id, context=context) part = obj_partner.browse(cr, uid, partner_id, context=context)
@@ -543,23 +567,25 @@ class AccountBankSatementLine(Model):
type = 'supplier' type = 'supplier'
if part.customer == True: if part.customer == True:
type = 'customer' type = 'customer'
res_type = self.onchange_type(cr, uid, ids, partner_id, type, profile_id, context=context) # Chg 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): if res_type['value'] and res_type['value'].get('account_id', False):
return {'value': {'type': type, 'account_id': res_type['value']['account_id']}} return {'value': {'type': type, 'account_id': res_type['value']['account_id']}}
return {'value': {'type': type}} return {'value': {'type': type}}
def onchange_type(self, cr, uid, line_id, partner_id, type, profile_id, context=None): 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, Keep the same features as in standard and call super. If an account is returned,
call the method to compute line values. call the method to compute line values.
""" """
if context is None: res = super(AccountBankSatementLine, self).onchange_type(
context = {} cr, uid, line_id, partner_id, type, context=context)
res = super(AccountBankSatementLine,self).onchange_type(cr, uid, line_id, partner_id, type, context)
if 'account_id' in res['value']: if 'account_id' in res['value']:
result = self.get_values_for_line(cr, uid, profile_id = profile_id, result = self.get_values_for_line(
partner_id = partner_id, line_type = type, context = context) cr, uid,
profile_id=profile_id,
partner_id=partner_id,
line_type=type,
context=context)
if result: if result:
res['value'].update({'account_id':result['account_id']}) res['value'].update({'account_id': result['account_id']})
return res return res

View File

@@ -24,8 +24,11 @@
'author': 'Camptocamp', 'author': 'Camptocamp',
'maintainer': 'Camptocamp', 'maintainer': 'Camptocamp',
'category': 'Finance', 'category': 'Finance',
'complexity': 'normal', #easy, normal, expert 'complexity': 'normal',
'depends': ['account_statement_ext','account_voucher'], 'depends': [
'account_statement_ext',
'account_voucher'
],
'description': """ 'description': """
This module is only needed when using account_bank_statement_ext with voucher in order to compute the period This module is only needed when using account_bank_statement_ext with voucher in order to compute the period
correctly. This is mainly because with account_bank_statement_ext, the period is computed for each line. correctly. This is mainly because with account_bank_statement_ext, the period is computed for each line.

View File

@@ -20,7 +20,7 @@
############################################################################## ##############################################################################
from openerp.osv.orm import Model from openerp.osv.orm import Model
from openerp.osv import fields, osv
class AccountVoucher(Model): class AccountVoucher(Model):
@@ -31,7 +31,8 @@ class AccountVoucher(Model):
if context is None: if context is None:
context = {} context = {}
if not context.get('period_id') and context.get('move_line_ids'): if not context.get('period_id') and context.get('move_line_ids'):
res = self.pool.get('account.move.line').browse(cr, uid , context.get('move_line_ids'))[0].period_id.id res = self.pool.get('account.move.line').browse(
cr, uid, context.get('move_line_ids'), context=context)[0].period_id.id
context['period_id'] = res context['period_id'] = res
elif context.get('date'): elif context.get('date'):
periods = self.pool.get('account.period').find( periods = self.pool.get('account.period').find(
@@ -48,4 +49,3 @@ class AccountVoucher(Model):
ctx = dict(context, date=values.get('date')) ctx = dict(context, date=values.get('date'))
values['period_id'] = self._get_period(cr, uid, ctx) values['period_id'] = self._get_period(cr, uid, ctx)
return super(AccountVoucher, self).create(cr, uid, values, context) return super(AccountVoucher, self).create(cr, uid, values, context)

View File

@@ -24,8 +24,11 @@
'author': 'Camptocamp', 'author': 'Camptocamp',
'maintainer': 'Camptocamp', 'maintainer': 'Camptocamp',
'category': 'Finance', 'category': 'Finance',
'complexity': 'normal', #easy, normal, expert 'complexity': 'normal',
'depends': ['account_statement_base_completion', 'base_transaction_id'], 'depends': [
'account_statement_base_completion',
'base_transaction_id'
],
'description': """ 'description': """
Add a completion method based on transaction ID providen by the bank/office. Add a completion method based on transaction ID providen by the bank/office.

View File

@@ -20,27 +20,27 @@
############################################################################## ##############################################################################
from openerp.tools.translate import _ from openerp.tools.translate import _
import datetime
import netsvc
from openerp.osv.orm import Model from openerp.osv.orm import Model
from openerp.osv import fields, osv from openerp.osv import fields
from openerp.addons.account_statement_base_completion.statement import ErrorTooManyPartner from openerp.addons.account_statement_base_completion.statement import ErrorTooManyPartner
class AccountStatementCompletionRule(Model): class AccountStatementCompletionRule(Model):
"""Add a rule based on transaction ID""" """Add a rule based on transaction ID"""
_inherit = "account.statement.completion.rule" _inherit = "account.statement.completion.rule"
def _get_functions(self, cr, uid, context=None): def _get_functions(self, cr, uid, context=None):
res = super (AccountStatementCompletionRule, self)._get_functions( res = super(AccountStatementCompletionRule, self)._get_functions(
cr, uid, context=context) cr, uid, context=context)
res.append(('get_from_transaction_id_and_so', 'From line reference (based on SO transaction ID)')) res.append(('get_from_transaction_id_and_so',
'From line reference (based on SO transaction ID)'))
return res return res
_columns={ _columns = {
'function_to_call': fields.selection(_get_functions, 'Method'), 'function_to_call': fields.selection(_get_functions, 'Method'),
} }
def get_from_transaction_id_and_so(self, cr, uid, line_id, context=None): 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. Match the partner based on the transaction ID field of the SO.
@@ -55,36 +55,45 @@ class AccountStatementCompletionRule(Model):
...} ...}
""" """
st_obj = self.pool.get('account.bank.statement.line') st_obj = self.pool.get('account.bank.statement.line')
st_line = st_obj.browse(cr,uid,line_id) st_line = st_obj.browse(cr, uid, line_id, context=context)
res = {} res = {}
if st_line: if st_line:
so_obj = self.pool.get('sale.order') so_obj = self.pool.get('sale.order')
so_id = so_obj.search(cr, uid, [('transaction_id', '=', st_line.transaction_id)]) so_id = so_obj.search(
cr,
uid,
[('transaction_id', '=', st_line.transaction_id)],
context=context)
if so_id and len(so_id) == 1: if so_id and len(so_id) == 1:
so = so_obj.browse(cr, uid, so_id[0]) so = so_obj.browse(cr, uid, so_id[0], context=context)
res['partner_id'] = so.partner_id.id res['partner_id'] = so.partner_id.id
res['ref'] = so.name res['ref'] = so.name
elif so_id and len(so_id) > 1: elif so_id and len(so_id) > 1:
raise ErrorTooManyPartner(_('Line named "%s" (Ref:%s) was matched by more than one partner.')%(st_line.name,st_line.ref)) raise ErrorTooManyPartner(
_('Line named "%s" (Ref:%s) was matched by more than '
'one partner.') % (st_line.name, st_line.ref))
if so_id: if so_id:
st_vals = st_obj.get_values_for_line(cr, uid, profile_id = st_line.statement_id.profile_id.id, st_vals = st_obj.get_values_for_line(
partner_id = res.get('partner_id',False), line_type = st_line.type, amount = st_line.amount, context=context) 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) res.update(st_vals)
return res return res
class AccountStatementLine(Model): class AccountStatementLine(Model):
_inherit = "account.bank.statement.line" _inherit = "account.bank.statement.line"
_columns={ _columns = {
# 'additionnal_bank_fields' : fields.serialized('Additionnal infos from bank', help="Used by completion and import system."), # 'additionnal_bank_fields' : fields.serialized('Additionnal infos from bank', help="Used by completion and import system."),
'transaction_id': fields.sparse(type='char', string='Transaction ID', 'transaction_id': fields.sparse(
type='char',
string='Transaction ID',
size=128, size=128,
serialization_field='additionnal_bank_fields', serialization_field='additionnal_bank_fields',
help="Transction id from the financial institute"), help="Transction id from the financial institute"),
} }

View File

@@ -19,4 +19,4 @@
# #
############################################################################## ##############################################################################
import parser import parser
import statement import statement

View File

@@ -24,8 +24,11 @@
'author': 'Camptocamp', 'author': 'Camptocamp',
'maintainer': 'Camptocamp', 'maintainer': 'Camptocamp',
'category': 'Finance', 'category': 'Finance',
'complexity': 'normal', #easy, normal, expert 'complexity': 'normal',
'depends': ['account_statement_base_import','account_statement_transactionid_completion'], 'depends': [
'account_statement_base_import',
'account_statement_transactionid_completion'
],
'description': """ 'description': """
This module brings generic methods and fields on bank statement to deal with This module brings generic methods and fields on bank statement to deal with
the importation of different bank and offices that uses transactionID. the importation of different bank and offices that uses transactionID.
@@ -36,12 +39,12 @@
This module can handle a commission taken by the payment office and has the following format: This module can handle a commission taken by the payment office and has the following format:
* transaction_id : the transaction ID given by the bank/office. It is used as reference * transaction_id: the transaction ID given by the bank/office. It is used as reference
in the generated entries and is useful for reconciliation process in the generated entries and is useful for reconciliation process
* date : date of the payment * date: date of the payment
* amount : amount paid in the currency of the journal used in the importation profile * amount: amount paid in the currency of the journal used in the importation profile
* commission_amount : amount of the comission for each line * commission_amount: amount of the comission for each line
* label : the comunication given by the payment office, used as communication in the * label: the comunication given by the payment office, used as communication in the
generated entries. generated entries.
""", """,
'website': 'http://www.camptocamp.com', 'website': 'http://www.camptocamp.com',

View File

@@ -19,6 +19,4 @@
# #
############################################################################## ##############################################################################
# from parser import new_bank_statement_parser import transactionid_file_parser
# from parser import BankStatementImportParser
import transactionid_file_parser

View File

@@ -19,23 +19,16 @@
############################################################################## ##############################################################################
from openerp.tools.translate import _ from openerp.tools.translate import _
import base64
import csv
import tempfile
import datetime import datetime
from account_statement_base_import.parser.file_parser import FileParser from account_statement_base_import.parser.file_parser import FileParser
try:
import xlrd
except:
raise Exception(_('Please install python lib xlrd'))
class TransactionIDFileParser(FileParser): class TransactionIDFileParser(FileParser):
""" """
TransactionID parser that use a define format in csv or xls to import TransactionID parser that use a define format in csv or xls to import
bank statement. bank statement.
""" """
def __init__(self, parse_name, ftype='csv'): def __init__(self, parse_name, ftype='csv'):
convertion_dict = { convertion_dict = {
'transaction_id': unicode, 'transaction_id': unicode,
@@ -46,7 +39,7 @@ class TransactionIDFileParser(FileParser):
} }
# Order of cols does not matter but first row of the file has to be header # 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'] 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) super(TransactionIDFileParser, self).__init__(parse_name, keys_to_validate=keys_to_validate, ftype=ftype, convertion_dict=convertion_dict)
@classmethod @classmethod
def parser_for(cls, parser_name): def parser_for(cls, parser_name):
@@ -59,7 +52,7 @@ class TransactionIDFileParser(FileParser):
def get_st_line_vals(self, line, *args, **kwargs): def get_st_line_vals(self, line, *args, **kwargs):
""" """
This method must return a dict of vals that can be passed to create 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 method of statement line in order to record it. It is the responsibility
of every parser to give this dict of vals, so each one can implement his of every parser to give this dict of vals, so each one can implement his
own way of recording the lines. own way of recording the lines.
:param: line: a dict of vals that represent a line of result_row_list :param: line: a dict of vals that represent a line of result_row_list
@@ -77,12 +70,12 @@ class TransactionIDFileParser(FileParser):
for each one. for each one.
""" """
return { return {
'name': line.get('label', line.get('ref','/')), 'name': line.get('label', line.get('ref', '/')),
'date': line.get('date', datetime.datetime.now().date()), 'date': line.get('date', datetime.datetime.now().date()),
'amount': line.get('amount', 0.0), 'amount': line.get('amount', 0.0),
'ref': line.get('transaction_id','/'), 'ref': line.get('transaction_id', '/'),
'label': line.get('label',''), 'label': line.get('label', ''),
'transaction_id': line.get('transaction_id','/'), 'transaction_id': line.get('transaction_id', '/'),
'commission_amount': line.get('commission_amount', 0.0), 'commission_amount': line.get('commission_amount', 0.0),
} }
@@ -93,7 +86,6 @@ class TransactionIDFileParser(FileParser):
res = super(TransactionIDFileParser, self)._post(*args, **kwargs) res = super(TransactionIDFileParser, self)._post(*args, **kwargs)
val = 0.0 val = 0.0
for row in self.result_row_list: for row in self.result_row_list:
val += row.get('commission_amount',0.0) val += row.get('commission_amount', 0.0)
self.commission_global_amount = val self.commission_global_amount = val
return res return res

View File

@@ -20,24 +20,28 @@
############################################################################## ##############################################################################
from openerp.osv.orm import Model from openerp.osv.orm import Model
from openerp.osv import fields, osv from openerp.osv import fields
class AccountStatementProfil(Model): class AccountStatementProfil(Model):
_inherit = "account.statement.profile" _inherit = "account.statement.profile"
def get_import_type_selection(self, cr, uid, context=None): def get_import_type_selection(self, cr, uid, context=None):
""" """
Has to be inherited to add parser Has to be inherited to add parser
""" """
res = super(AccountStatementProfil, self).get_import_type_selection(cr, uid, context=context) res = super(AccountStatementProfil, self).get_import_type_selection(
res.append(('generic_csvxls_transaction','Generic .csv/.xls based on SO transaction ID')) cr, uid, context=context)
res.append(('generic_csvxls_transaction',
'Generic .csv/.xls based on SO transaction ID'))
return res return res
_columns = { _columns = {
'import_type': fields.selection(get_import_type_selection, 'Type of import', required=True, 'import_type': fields.selection(
help = "Choose here the method by which you want to import bank statement for this profile."), get_import_type_selection,
'Type of import',
required=True,
help="Choose here the method by which you want to import "
"bank statement for this profile."),
} }

View File

@@ -24,18 +24,30 @@
'author': 'Camptocamp', 'author': 'Camptocamp',
'maintainer': 'Camptocamp', 'maintainer': 'Camptocamp',
'category': 'Hidden/Dependency', 'category': 'Hidden/Dependency',
'complexity': 'easy', #easy, normal, expert 'complexity': 'easy',
'depends': ['account', 'sale','stock'], 'depends': [
'account',
'sale',
'stock'
],
'description': """ 'description': """
Adds transaction id to invoice and sale models and views. On Sales order, you can specify the transaction ID Adds transaction id to invoice and sale models and views.
used for the payment and it will be propagated to the invoice (even if made from packing). On Sales order, you can specify the transaction ID used
This is mostly used for e-commerce handling. You can then add a mapping on that SO field to save the e-commerce for the payment and it will be propagated to the invoice
financial Transaction ID into the OpenERP SO field. The main purpose is to ease the reconciliation process and (even if made from packing).
This is mostly used for e-commerce handling.
You can then add a mapping on that SO field to save
the e-commerce financial Transaction ID into the
OpenERP sale order field.
The main purpose is to ease the reconciliation process and
be able to find the partner when importing the bank statement. be able to find the partner when importing the bank statement.
""", """,
'website': 'http://www.openerp.com', 'website': 'http://www.openerp.com',
'init_xml': [], 'init_xml': [],
'update_xml': ['invoice_view.xml', 'sale_view.xml'], 'update_xml': [
'invoice_view.xml',
'sale_view.xml'
],
'demo_xml': [], 'demo_xml': [],
'test': [], 'test': [],
'installable': True, 'installable': True,

View File

@@ -20,17 +20,17 @@
############################################################################## ##############################################################################
from openerp.osv.orm import Model from openerp.osv.orm import Model
from openerp.osv import fields, osv from openerp.osv import fields
from openerp.tools.translate import _
class AccountInvoice(Model): class AccountInvoice(Model):
_inherit = 'account.invoice' _inherit = 'account.invoice'
_columns = { _columns = {
'transaction_id':fields.char( 'transaction_id': fields.char(
'Transaction id', 'Transaction id',
size=128, size=128,
required=False, required=False,
select=1, select=1,
help="Transction id from the financial institute" help="Transction id from the financial institute"),
),
} }

View File

@@ -20,21 +20,24 @@
############################################################################## ##############################################################################
from openerp.osv.orm import Model from openerp.osv.orm import Model
from openerp.osv import fields, osv from openerp.osv import fields
class SaleOrder(Model): class SaleOrder(Model):
_inherit = 'sale.order' _inherit = 'sale.order'
_columns = {
'transaction_id':fields.char('Transaction id', size=128,required=False,
help="Transction id from the financial institute"),
}
_columns = {
'transaction_id': fields.char(
'Transaction id',
size=128,
required=False,
help="Transaction id from the financial institute"),
}
def _prepare_invoice(self, cursor, uid, order, lines, context=None): def _prepare_invoice(self, cursor, uid, order, lines, context=None):
#we put the transaction id in the generated invoices #we put the transaction id in the generated invoices
if context is None: invoice_vals = super(SaleOrder, self)._prepare_invoice(
context = {} cursor, uid, order, lines, context=context)
invoice_vals = super(SaleOrder, self)._prepare_invoice(cursor, uid, order, lines, context)
invoice_vals.update({ invoice_vals.update({
'transaction_id': order.transaction_id}) 'transaction_id': order.transaction_id})
return invoice_vals return invoice_vals

View File

@@ -20,20 +20,23 @@
############################################################################## ##############################################################################
from openerp.osv.orm import Model from openerp.osv.orm import Model
from openerp.osv import fields, osv
class StockPicking(Model): class StockPicking(Model):
_inherit = "stock.picking" _inherit = "stock.picking"
def action_invoice_create(self, cursor, uid, ids, journal_id=False, def action_invoice_create(
group=False, type='out_invoice', context=None): self, cursor, uid, ids, journal_id=False, group=False,
res = super(StockPicking, self).action_invoice_create(cursor, uid, ids, type='out_invoice', context=None):
journal_id,group, type, context) res = super(StockPicking, self).action_invoice_create(
cursor, uid, ids, journal_id, group, type, context)
for pick_id in res: for pick_id in res:
pick = self.browse(cursor, uid, pick_id) pick = self.browse(cursor, uid, pick_id, context=context)
if pick.sale_id and pick.sale_id.transaction_id: if pick.sale_id and pick.sale_id.transaction_id:
self.pool.get('account.invoice').write(cursor, self.pool.get('account.invoice').write(
uid, cursor,
res[pick_id], uid,
{'transaction_id': pick.sale_id.transaction_id}) res[pick_id],
{'transaction_id': pick.sale_id.transaction_id},
context=context)
return res return res