From 35507cd40c2f3df27448d066d2ac2763d0eff1b2 Mon Sep 17 00:00:00 2001 From: Virgil Dupras Date: Fri, 3 May 2013 15:57:26 -0400 Subject: [PATCH 1/3] [ENH] Began extracting the commission-handling feature from account_statement_base_import. The extracted commission part is not functional, but the base_import module still works. --- .../parser/file_parser.py | 30 ++--- .../parser/generic_file_parser.py | 46 ++------ .../parser/parser.py | 15 --- account_statement_base_import/statement.py | 108 ++++++------------ account_statement_commission/commission.py | 44 +++++++ 5 files changed, 105 insertions(+), 138 deletions(-) create mode 100644 account_statement_commission/commission.py diff --git a/account_statement_base_import/parser/file_parser.py b/account_statement_base_import/parser/file_parser.py index 938863a3..54f7a4bb 100644 --- a/account_statement_base_import/parser/file_parser.py +++ b/account_statement_base_import/parser/file_parser.py @@ -28,37 +28,41 @@ try: except: raise Exception(_('Please install python lib xlrd')) +def float_or_zero(val): + """ Conversion function used to manage + empty string into float usecase""" + return float(val) if val else 0.0 class FileParser(BankStatementImportParser): """ Generic abstract class for defining parser for .csv or .xls file format. """ - def __init__(self, parse_name, keys_to_validate=None, ftype='csv', conversion_dict=None, - header=None, *args, **kwargs): + def __init__(self, parse_name, ftype='csv', extra_fields=None, header=None, **kwargs): """ :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 char ftype: extension of the file (could be csv or xls) - :param: conversion_dict : keys and type to convert of every column in the file like - { - 'ref': unicode, - 'label': unicode, - 'date': datetime.datetime, - 'amount': float, - 'commission_amount': float - } + :param dict extra_fields: extra fields to add to the conversion dict. In the format + {fieldname: fieldtype} :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, **kwargs) if ftype in ('csv', 'xls'): self.ftype = ftype else: raise except_osv(_('User Error'), _('Invalid file type %s. Please use csv or xls') % ftype) - self.keys_to_validate = keys_to_validate if keys_to_validate is not None else [] - self.conversion_dict = conversion_dict + self.conversion_dict = { + 'ref': unicode, + 'label': unicode, + 'date': datetime.datetime, + 'amount': float_or_zero, + } + if extra_fields: + self.conversion_dict.update(extra_fields) + self.keys_to_validate = self.conversion_dict.keys() self.fieldnames = header self._datemode = 0 # used only for xls documents, # 0 means Windows mode (1900 based dates). diff --git a/account_statement_base_import/parser/generic_file_parser.py b/account_statement_base_import/parser/generic_file_parser.py index 7d04bfab..81e51c8c 100644 --- a/account_statement_base_import/parser/generic_file_parser.py +++ b/account_statement_base_import/parser/generic_file_parser.py @@ -30,12 +30,6 @@ except: raise Exception(_('Please install python lib xlrd')) -def float_or_zero(val): - """ Conversion function used to manage - empty string into float usecase""" - return float(val) if val else 0.0 - - class GenericFileParser(FileParser): """ Standard parser that use a define format in csv or xls to import into a @@ -43,17 +37,8 @@ class GenericFileParser(FileParser): parser, but will also be useful as it allow to import a basic flat file. """ - def __init__(self, parse_name, ftype='csv'): - conversion_dict = { - 'ref': unicode, - 'label': unicode, - 'date': datetime.datetime, - 'amount': float_or_zero, - 'commission_amount': float_or_zero - } - # Order of cols does not matter but first row of the file has to be header - keys_to_validate = ['ref', 'label', 'date', 'amount', 'commission_amount'] - super(GenericFileParser, self).__init__(parse_name, keys_to_validate=keys_to_validate, ftype=ftype, conversion_dict=conversion_dict) + def __init__(self, parse_name, ftype='csv', **kwargs): + super(GenericFileParser, self).__init__(parse_name, ftype=ftype, **kwargs) @classmethod def parser_for(cls, parser_name): @@ -78,25 +63,12 @@ class GenericFileParser(FileParser): 'amount':value, 'ref':value, 'label':value, - 'commission_amount':value, } - In this generic parser, the commission is given for every line, so we store it - for each one. """ - return {'name': line.get('label', line.get('ref', '/')), - 'date': line.get('date', datetime.datetime.now().date()), - 'amount': line.get('amount', 0.0), - 'ref': line.get('ref', '/'), - 'label': line.get('label', ''), - 'commission_amount': line.get('commission_amount', 0.0)} - - def _post(self, *args, **kwargs): - """ - Compute the commission from value of each line - """ - res = super(GenericFileParser, self)._post(*args, **kwargs) - val = 0.0 - for row in self.result_row_list: - val += row.get('commission_amount', 0.0) - self.commission_global_amount = val - return res + return { + 'name': line.get('label', line.get('ref', '/')), + 'date': line.get('date', datetime.datetime.now().date()), + 'amount': line.get('amount', 0.0), + 'ref': line.get('ref', '/'), + 'label': line.get('label', ''), + } diff --git a/account_statement_base_import/parser/parser.py b/account_statement_base_import/parser/parser.py index 6d6d0c25..377548b2 100644 --- a/account_statement_base_import/parser/parser.py +++ b/account_statement_base_import/parser/parser.py @@ -49,9 +49,6 @@ class BankStatementImportParser(object): self.result_row_list = None # The file buffer on which to work on self.filebuffer = None - # Concatenate here the global commission taken by the bank/office - # for this statement. - self.commission_global_amount = None @classmethod def parser_for(cls, parser_name): @@ -110,8 +107,6 @@ class BankStatementImportParser(object): """ Implement a method in your parser to make some last changes on the result of parsing the datas, like converting dates, computing commission, ... - Work on self.result_row_list and put the commission global amount if any - in the self.commission_global_amount one. """ return NotImplementedError @@ -133,16 +128,6 @@ class BankStatementImportParser(object): """ return NotImplementedError - def get_st_line_commision(self, *args, **kwargs): - """ - This is called by the importation method to create the commission line in - the bank statement. We will always create one line for the commission in the - bank statement, but it could be computated from a value of each line, or given - in a single line for the whole file. - return: float of the whole commission (self.commission_global_amount) - """ - return self.commission_global_amount - def parse(self, filebuffer, *args, **kwargs): """ This will be the method that will be called by wizard, button and so diff --git a/account_statement_base_import/statement.py b/account_statement_base_import/statement.py index 69b725f2..3b7998cc 100644 --- a/account_statement_base_import/statement.py +++ b/account_statement_base_import/statement.py @@ -56,6 +56,21 @@ class AccountStatementProfil(Model): } + def _write_extra_statement_lines( + self, cr, uid, parser, result_row_list, profile, statement_id, context): + """Insert extra lines after the main statement lines. + + After the main statement lines have been created, you can override this method to create + extra statement lines. + + :param: browse_record of the current parser + :param: result_row_list: [{'key':value}] + :param: profile: browserecord of account.statement.profile + :param: statement_id: int/long of the current importing statement ID + :param: context: global context + """ + pass + def write_logs_after_import(self, cr, uid, ids, statement_id, num_lines, context): """ Write the log in the logger @@ -72,41 +87,6 @@ class AccountStatementProfil(Model): context=context) return True - def prepare_global_commission_line_vals( - self, cr, uid, parser, result_row_list, profile, statement_id, context): - """ - Prepare the global commission line if there is one. The global - commission is computed by by calling the get_st_line_commision - of the parser. Feel free to override the method to compute - your own commission line from the result_row_list. - - :param: browse_record of the current parser - :param: result_row_list: [{'key':value}] - :param: profile: browserecord of account.statement.profile - :param: statement_id: int/long of the current importing statement ID - :param: context: global context - return: dict of vals that will be passed to create method of statement line. - """ - comm_values = False - if parser.get_st_line_commision(): - partner_id = profile.partner_id and profile.partner_id.id or False - commission_account_id = profile.commission_account_id and profile.commission_account_id.id or False - commission_analytic_id = profile.commission_analytic_id and profile.commission_analytic_id.id or False - comm_values = { - 'name': 'IN ' + _('Commission line'), - 'date': datetime.datetime.now().date(), - 'amount': parser.get_st_line_commision(), - 'partner_id': partner_id, - 'type': 'general', - 'statement_id': statement_id, - 'account_id': commission_account_id, - 'ref': 'commission', - 'analytic_account_id': commission_analytic_id, - # !! We set the already_completed so auto-completion will not update those values! - 'already_completed': True, - } - return comm_values - def prepare_statetement_lines_vals( self, cr, uid, parser_vals, account_payable, account_receivable, statement_id, context): @@ -153,8 +133,6 @@ class AccountStatementProfil(Model): Create a bank statement with the given profile and parser. It will fullfill the bank statement with the values of the file providen, but will not complete data (like finding the partner, or the right account). This will be done in a second step with the completion rules. - It will also create the commission line if it apply and record the providen file as - an attachement of the bank statement. :param int/long profile_id: ID of the profile used to import the file :param filebuffer file_stream: binary of the providen file @@ -192,41 +170,33 @@ class AccountStatementProfil(Model): account_receivable, account_payable = statement_obj.get_default_pay_receiv_accounts( cr, uid, context) try: - # Record every line in the bank statement and compute the global commission - # based on the commission_amount column + # Record every line in the bank statement statement_store = [] for line in result_row_list: parser_vals = parser.get_st_line_vals(line) - values = self.prepare_statetement_lines_vals(cr, uid, parser_vals, account_payable, - account_receivable, statement_id, context) + values = self.prepare_statetement_lines_vals( + cr, uid, parser_vals, account_payable, account_receivable, statement_id, + context) statement_store.append(values) # Hack to bypass ORM poor perfomance. Sob... statement_line_obj._insert_lines(cr, uid, statement_store, context=context) - # Build and create the global commission line for the whole statement - comm_vals = self.prepare_global_commission_line_vals(cr, uid, parser, result_row_list, - prof, statement_id, context) - if comm_vals: - statement_line_obj.create(cr, uid, comm_vals, context=context) - else: - # Trigger store field computation if someone has better idea - start_bal = statement_obj.read(cr, uid, statement_id, - ['balance_start'], - context=context) - start_bal = start_bal['balance_start'] - statement_obj.write(cr, uid, [statement_id], - {'balance_start': start_bal}) + self._write_extra_statement_lines( + cr, uid, parser, result_row_list, prof, statement_id, context) + # Trigger store field computation if someone has better idea + start_bal = statement_obj.read( + cr, uid, statement_id, ['balance_start'], context=context) + start_bal = start_bal['balance_start'] + statement_obj.write(cr, uid, [statement_id], {'balance_start': start_bal}) - attachment_obj.create(cr, - uid, - {'name': 'statement file', - 'datas': file_stream, - 'datas_fname': "%s.%s" % ( - datetime.datetime.now().date(), - ftype), - 'res_model': 'account.bank.statement', - 'res_id': statement_id}, - context=context) + attachment_data = { + 'name': 'statement file', + 'datas': file_stream, + 'datas_fname': "%s.%s" % (datetime.datetime.now().date(), ftype), + 'res_model': 'account.bank.statement', + 'res_id': statement_id, + } + attachment_obj.create(cr, uid, attachment_data, context=context) # If user ask to launch completion at end of import, do it! if prof.launch_import_completion: @@ -251,8 +221,7 @@ class AccountStatementProfil(Model): class AccountStatementLine(Model): """ Add sparse field on the statement line to allow to store all the - bank infos that are given by an office. In this basic sample case - it concern only commission_amount. + bank infos that are given by an office. """ _inherit = "account.bank.statement.line" @@ -294,10 +263,3 @@ class AccountStatementLine(Model): cr.rollback() raise osv.except_osv(_("ORM bypass error"), sql_err.pgerror) - - _columns = { - 'commission_amount': fields.sparse( - type='float', - string='Line Commission Amount', - serialization_field='additionnal_bank_fields'), - } diff --git a/account_statement_commission/commission.py b/account_statement_commission/commission.py new file mode 100644 index 00000000..ce928b41 --- /dev/null +++ b/account_statement_commission/commission.py @@ -0,0 +1,44 @@ +from openerp.tools.translate import _ +import datetime +from openerp.osv import fields +from openerp.osv.orm import Model + +class AccountStatementProfil(Model): + _inherit = "account.statement.profile" + + def _write_extra_statement_lines( + self, cr, uid, parser, result_row_list, profile, statement_id, context): + """Prepare the global commission line if there is one. + """ + global_commission_amount = 0 + for row in parser.result_row_list: + global_commission_amount += row.get('commission_amount', 0.0) + if not global_commission_amount: + return + partner_id = profile.partner_id and profile.partner_id.id or False + commission_account_id = profile.commission_account_id and profile.commission_account_id.id or False + commission_analytic_id = profile.commission_analytic_id and profile.commission_analytic_id.id or False + comm_values = { + 'name': 'IN ' + _('Commission line'), + 'date': datetime.datetime.now().date(), + 'amount': global_commission_amount, + 'partner_id': partner_id, + 'type': 'general', + 'statement_id': statement_id, + 'account_id': commission_account_id, + 'ref': 'commission', + 'analytic_account_id': commission_analytic_id, + # !! We set the already_completed so auto-completion will not update those values! + 'already_completed': True, + } + statement_line_obj = self.pool.get('account.bank.statement.line') + statement_line_obj.create(cr, uid, comm_values, context=context) + +class AccountStatementLineWithCommission(Model): + _inherit = "account.bank.statement.line" + _columns = { + 'commission_amount': fields.sparse( + type='float', + string='Line Commission Amount', + serialization_field='additionnal_bank_fields'), + } From 18f4ac078527af8a6ecc84d0f48a44f8de6af24a Mon Sep 17 00:00:00 2001 From: Virgil Dupras Date: Wed, 12 Jun 2013 13:54:18 -0400 Subject: [PATCH 2/3] [FIX] Moved commission-related view inheritance out of account_statement_base_import. The commission field having been extracted out of the module, trying to show it in the Account Statement view would prevent that view from working properly. --- .../statement_view.xml | 16 -------------- .../statement_view.xml | 21 +++++++++++++++++++ 2 files changed, 21 insertions(+), 16 deletions(-) create mode 100644 account_statement_commission/statement_view.xml diff --git a/account_statement_base_import/statement_view.xml b/account_statement_base_import/statement_view.xml index 23ff0cc3..19c5aa8a 100644 --- a/account_statement_base_import/statement_view.xml +++ b/account_statement_base_import/statement_view.xml @@ -26,21 +26,5 @@ - - account_bank_statement.bank_statement.view_form - account.bank.statement - - form - - - - - - - - - - - diff --git a/account_statement_commission/statement_view.xml b/account_statement_commission/statement_view.xml new file mode 100644 index 00000000..7da6c27c --- /dev/null +++ b/account_statement_commission/statement_view.xml @@ -0,0 +1,21 @@ + + + + + + account_bank_statement.bank_statement.view_form + account.bank.statement + + form + + + + + + + + + + + + From e76fe72667993bf4154da24c6bd35c59a5058a4e Mon Sep 17 00:00:00 2001 From: Virgil Dupras Date: Mon, 8 Jul 2013 14:03:34 -0400 Subject: [PATCH 3/3] [IMP] Completed commission extraction from account_statement_base_import. Removed the last remnants of commission handling from account_statement_base_import and made the newly created account_statement_commission module work properly. --- account_statement_base_import/__openerp__.py | 1 - .../wizard/import_statement.py | 7 --- .../wizard/import_statement_view.xml | 2 - account_statement_commission/__init__.py | 22 +++++++++ account_statement_commission/__openerp__.py | 47 +++++++++++++++++++ account_statement_commission/commission.py | 34 ++++++++++++-- .../import_statement_view.xml | 18 +++++++ 7 files changed, 116 insertions(+), 15 deletions(-) create mode 100644 account_statement_commission/__init__.py create mode 100644 account_statement_commission/__openerp__.py create mode 100644 account_statement_commission/import_statement_view.xml diff --git a/account_statement_base_import/__openerp__.py b/account_statement_base_import/__openerp__.py index c9f6e1b5..086af048 100644 --- a/account_statement_base_import/__openerp__.py +++ b/account_statement_base_import/__openerp__.py @@ -47,7 +47,6 @@ in the generated entries and will be useful for reconciliation process * date : date of the payment * amount : amount paid in the currency of the journal used in the importation profile - * commission_amount : amount of the comission for each line * label : the comunication given by the payment office, used as communication in the generated entries. diff --git a/account_statement_base_import/wizard/import_statement.py b/account_statement_base_import/wizard/import_statement.py index 80ea291e..b6960ec9 100644 --- a/account_statement_base_import/wizard/import_statement.py +++ b/account_statement_base_import/wizard/import_statement.py @@ -55,10 +55,6 @@ class CreditPartnerStatementImporter(orm.TransientModel): 'journal_id': fields.many2one('account.journal', 'Financial journal to use transaction'), 'file_name': fields.char('File Name', size=128), - 'commission_account_id': fields.many2one('account.account', - 'Commission account'), - 'commission_analytic_id': fields.many2one('account.analytic.account', - 'Commission analytic account'), 'receivable_account_id': fields.many2one('account.account', 'Force Receivable/Payable Account'), 'force_partner_on_bank': fields.boolean( @@ -80,10 +76,7 @@ class CreditPartnerStatementImporter(orm.TransientModel): res = {'value': {'partner_id': c.partner_id and c.partner_id.id or False, 'journal_id': c.journal_id and c.journal_id.id or False, - 'commission_account_id': - c.commission_account_id and c.commission_account_id.id or False, 'receivable_account_id': c.receivable_account_id and c.receivable_account_id.id or False, - 'commission_a': c.commission_analytic_id and c.commission_analytic_id.id or False, 'force_partner_on_bank': c.force_partner_on_bank, 'balance_check': c.balance_check, } diff --git a/account_statement_base_import/wizard/import_statement_view.xml b/account_statement_base_import/wizard/import_statement_view.xml index 57fd8476..51ec4740 100644 --- a/account_statement_base_import/wizard/import_statement_view.xml +++ b/account_statement_base_import/wizard/import_statement_view.xml @@ -14,8 +14,6 @@ - - diff --git a/account_statement_commission/__init__.py b/account_statement_commission/__init__.py new file mode 100644 index 00000000..5ba29072 --- /dev/null +++ b/account_statement_commission/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Joel Grand-Guillaume +# Copyright 2011-2012 Camptocamp SA +# Copyright 2013 Savoir-faire Linux () +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +import commission diff --git a/account_statement_commission/__openerp__.py b/account_statement_commission/__openerp__.py new file mode 100644 index 00000000..9d22eab5 --- /dev/null +++ b/account_statement_commission/__openerp__.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Author: Joel Grand-Guillaume +# Copyright 2011-2012 Camptocamp SA +# Copyright 2013 Savoir-faire Linux () +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +{ + 'name': "Bank statement import - commissions", + 'version': '1.0', + 'author': 'Camptocamp', + 'maintainer': 'Camptocamp', + 'category': 'Finance', + 'complexity': 'normal', + 'depends': [ + 'account_statement_base_import' + ], + 'description': """ +This module brings commission support to bank statement imports. It computes the sum of a commission +field on each transaction and creates a statement entry for it. +""", +'website': 'http://www.camptocamp.com', +'data': [ + "statement_view.xml", + "import_statement_view.xml", +], +'test': [], +'installable': True, +'images': [], +'auto_install': False, +'license': 'AGPL-3', +} diff --git a/account_statement_commission/commission.py b/account_statement_commission/commission.py index ce928b41..138ad61f 100644 --- a/account_statement_commission/commission.py +++ b/account_statement_commission/commission.py @@ -1,9 +1,11 @@ from openerp.tools.translate import _ import datetime -from openerp.osv import fields -from openerp.osv.orm import Model +from openerp.osv import orm, fields -class AccountStatementProfil(Model): +def float_or_zero(val): + return float(val) if val else 0.0 + +class AccountStatementProfil(orm.Model): _inherit = "account.statement.profile" def _write_extra_statement_lines( @@ -12,7 +14,7 @@ class AccountStatementProfil(Model): """ global_commission_amount = 0 for row in parser.result_row_list: - global_commission_amount += row.get('commission_amount', 0.0) + global_commission_amount += float_or_zero(row.get('commission_amount', '0.0')) if not global_commission_amount: return partner_id = profile.partner_id and profile.partner_id.id or False @@ -34,7 +36,7 @@ class AccountStatementProfil(Model): statement_line_obj = self.pool.get('account.bank.statement.line') statement_line_obj.create(cr, uid, comm_values, context=context) -class AccountStatementLineWithCommission(Model): +class AccountStatementLineWithCommission(orm.Model): _inherit = "account.bank.statement.line" _columns = { 'commission_amount': fields.sparse( @@ -42,3 +44,25 @@ class AccountStatementLineWithCommission(Model): string='Line Commission Amount', serialization_field='additionnal_bank_fields'), } + +class CreditPartnerStatementImporter(orm.TransientModel): + _inherit = "credit.statement.import" + + _columns = { + 'commission_account_id': fields.many2one('account.account', + 'Commission account'), + 'commission_analytic_id': fields.many2one('account.analytic.account', + 'Commission analytic account'), + } + + def onchange_profile_id(self, cr, uid, ids, profile_id, context=None): + res = super(CreditPartnerStatementImporter, self).onchange_profile_id( + cr, uid, ids, profile_id, context=context) + if profile_id: + c = self.pool.get("account.statement.profile").browse( + cr, uid, profile_id, context=context) + res['value']['commission_account_id'] = \ + c.commission_account_id and c.commission_account_id.id or False + res['value']['commission_a'] = \ + c.commission_analytic_id and c.commission_analytic_id.id or False + return res \ No newline at end of file diff --git a/account_statement_commission/import_statement_view.xml b/account_statement_commission/import_statement_view.xml new file mode 100644 index 00000000..45413b4f --- /dev/null +++ b/account_statement_commission/import_statement_view.xml @@ -0,0 +1,18 @@ + + + + + credit.statement.import.config.view + credit.statement.import + form + + + + + + + + + + +