~camptocamp/banking-addons/bank-statement-reconcile_vre

« back to all changes in this revision

Viewing changes to account_statement_base_import/parser/parser.py

  • Committer: Joël Grand-Guillaume
  • Date: 2012-06-20 14:10:01 UTC
  • Revision ID: joel.grandguillaume@camptocamp.com-20120620141001-h5e7m6hyh1mophdo
[MRG] Add all the bank statement improvements that we made. This is mostly based on :
  account_statement_ext -> provide profile per bank statement, remove period, choose to use balance check or not,...
  account_statement_base_completion -> provide a completion rule system to fullfill the bank statement (partner, account,...)
  account_statement_base_import -> provide a base to create your own file parser for each bank/office and link it to a profile
  account_statement_transactionid_completion and account_statement_transactionid_import to use the transaction ID recorded in th SO
  account_advanced_reconcile -> An advanced way to setup reconciliation rules on every account
  account_financial_report_webkit -> some little fixes
(lp:c2c-financial-addons/6.1 rev 63)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
##############################################################################
 
3
#
 
4
#    Author: Joel Grand-Guillaume
 
5
#    Copyright 2011-2012 Camptocamp SA
 
6
#
 
7
#    This program is free software: you can redistribute it and/or modify
 
8
#    it under the terms of the GNU Affero General Public License as
 
9
#    published by the Free Software Foundation, either version 3 of the
 
10
#    License, or (at your option) any later version.
 
11
#
 
12
#    This program is distributed in the hope that it will be useful,
 
13
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
15
#    GNU Affero General Public License for more details.
 
16
#
 
17
#    You should have received a copy of the GNU Affero General Public License
 
18
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
19
#
 
20
##############################################################################
 
21
import base64
 
22
import csv
 
23
 
 
24
def UnicodeDictReader(utf8_data, **kwargs):
 
25
    csv_reader = csv.DictReader(utf8_data, **kwargs)
 
26
    for row in csv_reader:
 
27
        yield dict([(key, unicode(value, 'utf-8')) for key, value in row.iteritems()])
 
28
 
 
29
class BankStatementImportParser(object):
 
30
    """
 
31
    Generic abstract class for defining parser for different files and
 
32
    format to import in a bank statement. Inherit from it to create your
 
33
    own. If your file is a .csv or .xls format, you should consider inheirt
 
34
    from the FileParser instead.
 
35
    """
 
36
    
 
37
    def __init__(self, parser_name, *args, **kwargs):
 
38
        # The name of the parser as it will be called
 
39
        self.parser_name = parser_name
 
40
        # The result as a list of row. One row per line of data in the file, but
 
41
        # not the commission one !
 
42
        self.result_row_list = None
 
43
        # The file buffer on which to work on
 
44
        self.filebuffer = None
 
45
        # Concatenate here the global commission taken by the bank/office
 
46
        # for this statement.
 
47
        self.commission_global_amount = None
 
48
           
 
49
    @classmethod
 
50
    def parser_for(cls, parser_name):
 
51
        """
 
52
        Override this method for every new parser, so that new_bank_statement_parser can
 
53
        return the good class from his name.
 
54
        """
 
55
        return False
 
56
            
 
57
    def _decode_64b_stream(self):
 
58
        """
 
59
        Decode self.filebuffer in base 64 and override it
 
60
        """
 
61
        self.filebuffer = base64.b64decode(self.filebuffer)
 
62
        return True
 
63
    
 
64
    def _format(self, decode_base_64=True, **kwargs):
 
65
        """
 
66
        Decode into base 64 if asked and Format the given filebuffer by calling 
 
67
        _custom_format method.
 
68
        """
 
69
        if decode_base_64:
 
70
            self._decode_64b_stream()
 
71
        self._custom_format(kwargs)
 
72
        return True
 
73
 
 
74
    def _custom_format(self, *args, **kwargs):
 
75
        """
 
76
        Implement a method in your parser to convert format, encoding and so on before
 
77
        starting to work on datas. Work on self.filebuffer
 
78
        """
 
79
        return NotImplementedError
 
80
 
 
81
    
 
82
    def _pre(self, *args, **kwargs):
 
83
        """
 
84
        Implement a method in your parser to make a pre-treatment on datas before parsing 
 
85
        them, like concatenate stuff, and so... Work on self.filebuffer
 
86
        """
 
87
        return NotImplementedError
 
88
 
 
89
    def _parse(self, *args, **kwargs):
 
90
        """
 
91
        Implement a method in your parser to save the result of parsing self.filebuffer 
 
92
        in self.result_row_list instance property.
 
93
        """
 
94
        return NotImplementedError
 
95
            
 
96
    def _validate(self, *args, **kwargs):
 
97
        """
 
98
        Implement a method in your parser  to validate the self.result_row_list instance
 
99
        property and raise an error if not valid.
 
100
        """
 
101
        return NotImplementedError
 
102
        
 
103
    def _post(self, *args, **kwargs):
 
104
        """
 
105
        Implement a method in your parser to make some last changes on the result of parsing
 
106
        the datas, like converting dates, computing commission, ...  
 
107
        Work on self.result_row_list and put the commission global amount if any
 
108
        in the self.commission_global_amount one.
 
109
        """
 
110
        return NotImplementedError
 
111
        
 
112
 
 
113
        
 
114
    def get_st_line_vals(self, line, *args, **kwargs):
 
115
        """
 
116
        Implement a method in your parser that must return a dict of vals that can be 
 
117
        passed to create method of statement line in order to record it. It is the responsibility 
 
118
        of every parser to give this dict of vals, so each one can implement his
 
119
        own way of recording the lines.
 
120
            :param:  line: a dict of vals that represent a line of result_row_list
 
121
            :return: dict of values to give to the create method of statement line,
 
122
                     it MUST contain at least:
 
123
                {
 
124
                    'name':value,
 
125
                    'date':value,
 
126
                    'amount':value,
 
127
                    'ref':value,
 
128
                }
 
129
        """
 
130
        return NotImplementedError
 
131
    
 
132
    def get_st_line_commision(self, *args, **kwargs):
 
133
        """
 
134
        This is called by the importation method to create the commission line in
 
135
        the bank statement. We will always create one line for the commission in the
 
136
        bank statement, but it could be computated from a value of each line, or given 
 
137
        in a single line for the whole file.
 
138
            return: float of the whole commission (self.commission_global_amount)
 
139
        """
 
140
        return self.commission_global_amount
 
141
    
 
142
    def parse(self, filebuffer, *args, **kwargs):
 
143
        """
 
144
        This will be the method that will be called by wizard, button and so
 
145
        to parse a filebuffer by calling successively all the private method
 
146
        that need to be define for each parser.
 
147
        Return:
 
148
             [] of rows as {'key':value}
 
149
             
 
150
        Note: The row_list must contain only value that are present in the account.
 
151
        bank.statement.line object !!!
 
152
        """
 
153
        if filebuffer:
 
154
            self.filebuffer = filebuffer
 
155
        else:
 
156
            raise Exception(_('No buffer file given.'))
 
157
        self._format(*args, **kwargs)
 
158
        self._pre(*args, **kwargs)
 
159
        self._parse(*args, **kwargs)
 
160
        self._validate(*args, **kwargs)
 
161
        self._post(*args, **kwargs)
 
162
        return self.result_row_list
 
163
               
 
164
def itersubclasses(cls, _seen=None):
 
165
    """
 
166
    itersubclasses(cls)
 
167
 
 
168
    Generator over all subclasses of a given class, in depth first order.
 
169
 
 
170
    >>> list(itersubclasses(int)) == [bool]
 
171
    True
 
172
    >>> class A(object): pass
 
173
    >>> class B(A): pass
 
174
    >>> class C(A): pass
 
175
    >>> class D(B,C): pass
 
176
    >>> class E(D): pass
 
177
    >>> 
 
178
    >>> for cls in itersubclasses(A):
 
179
    ...     print(cls.__name__)
 
180
    B
 
181
    D
 
182
    E
 
183
    C
 
184
    >>> # get ALL (new-style) classes currently defined
 
185
    >>> [cls.__name__ for cls in itersubclasses(object)] #doctest: +ELLIPSIS
 
186
    ['type', ...'tuple', ...]
 
187
    """
 
188
    if not isinstance(cls, type):
 
189
        raise TypeError('itersubclasses must be called with '
 
190
                        'new-style classes, not %.100r' % cls)
 
191
    if _seen is None: _seen = set()
 
192
    try:
 
193
        subs = cls.__subclasses__()
 
194
    except TypeError: # fails only when cls is type
 
195
        subs = cls.__subclasses__(cls)
 
196
    for sub in subs:
 
197
        if sub not in _seen:
 
198
            _seen.add(sub)
 
199
            yield sub
 
200
            for sub in itersubclasses(sub, _seen):
 
201
                yield sub
 
202
                        
 
203
def new_bank_statement_parser(parser_name, *args, **kwargs):
 
204
    """
 
205
    Return an instance of the good parser class base on the providen name
 
206
    :param char: parser_name
 
207
    :return: class instance of parser_name providen.
 
208
    """
 
209
    for cls in itersubclasses(BankStatementImportParser):
 
210
        if cls.parser_for(parser_name):
 
211
            return cls(parser_name, *args, **kwargs)
 
212
    raise ValueError
 
213
            
 
 
b'\\ No newline at end of file'