~ressu/+junk/account_banking_patu

« back to all changes in this revision

Viewing changes to account_banking/parsers/models.py

  • Committer: Sami Haahtinen
  • Date: 2010-08-02 11:19:59 UTC
  • Revision ID: ressu@ressukka.net-20100802111959-7bdrj9t13eg4g0oj
split repository

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- encoding: utf-8 -*-
2
 
##############################################################################
3
 
#
4
 
#  Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
5
 
#  All Rights Reserved
6
 
#
7
 
#  This program is free software: you can redistribute it and/or modify
8
 
#  it under the terms of the GNU General Public License as published by
9
 
#  the Free Software Foundation, either version 3 of the License, or
10
 
#  (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 General Public License for more details.
16
 
#
17
 
#  You should have received a copy of the GNU General Public License
18
 
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
 
#
20
 
##############################################################################
21
 
 
22
 
from tools.translate import _
23
 
 
24
 
class mem_bank_statement(object):
25
 
    '''
26
 
    A mem_bank_statement is a real life projection of a bank statement paper
27
 
    containing a report of one or more transactions done. As these reports can
28
 
    contain payments that originate in several accounting periods, period is an
29
 
    attribute of mem_bank_transaction, not of mem_bank_statement.
30
 
    Also note that the statement_id is copied from the bank statement, and not
31
 
    generated from any sequence. This enables us to skip old data in new
32
 
    statement files.
33
 
    '''
34
 
    # Lock attributes to enable parsers to trigger non-conformity faults
35
 
    __slots__ = [
36
 
        'start_balance','end_balance', 'date', 'local_account',
37
 
        'local_currency', 'id', 'statements'
38
 
    ]
39
 
    def __init__(self, *args, **kwargs):
40
 
        super(mem_bank_statement, self).__init__(*args, **kwargs)
41
 
        self.id = ''
42
 
        self.local_account = ''
43
 
        self.local_currency = ''
44
 
        self.start_balance = 0.0
45
 
        self.end_balance = 0.0
46
 
        self.date = ''
47
 
        self.transactions = []
48
 
 
49
 
    def is_valid(self):
50
 
        '''
51
 
        Final check: ok if calculated end_balance and parsed end_balance are
52
 
        identical and perform a heuristic check on the transactions.
53
 
        '''
54
 
        if any([x for x in self.transactions if not x.is_valid()]):
55
 
            return False
56
 
        check = float(self.start_balance)
57
 
        for transaction in self.transactions:
58
 
            check += float(transaction.transferred_amount)
59
 
        return abs(check - float(self.end_balance)) < 0.0001
60
 
 
61
 
class mem_bank_transaction(object):
62
 
    '''
63
 
    A mem_bank_transaction is a real life copy of a bank transfer. Mapping to
64
 
    OpenERP moves and linking to invoices and the like is done afterwards.
65
 
    '''
66
 
    # Lock attributes to enable parsers to trigger non-conformity faults
67
 
    __slots__ = [
68
 
        'id', 'local_account', 'local_currency', 'execution_date',
69
 
        'effective_date', 'remote_owner', 'remote_account',
70
 
        'remote_currency', 'transferred_amount', 'transfer_type',
71
 
        'reference', 'message', 'statement_id',
72
 
    ]
73
 
 
74
 
    # transfer_type's to be used by the business logic.
75
 
    # Depending on the type your parser gives a transaction, different
76
 
    # behavior can be triggered in the business logic.
77
 
    #
78
 
    #   BANK_COSTS          Automated credited costs by the bank.
79
 
    #                       Used to generate an automated invoice from the bank
80
 
    #                       Will be excluded from matching.
81
 
    #   BANK_TERMINAL       A cash withdrawal from a bank terminal.
82
 
    #                       Will be excluded from matching.
83
 
    #   CHECK               A delayed payment. Can be used to trigger extra
84
 
    #                       moves from temporary accounts. (Money away).
85
 
    #                       TODO: No special treatment yet.
86
 
    #                       Will be selected for matching.
87
 
    #   DIRECT_DEBIT        Speaks for itself. When outgoing (credit) and
88
 
    #                       matched, can signal the matched invoice triaged.
89
 
    #                       Will be selected for matching.
90
 
    #   ORDER               Order to the bank. Can work both ways.
91
 
    #                       Will be selected for matching.
92
 
    #   PAYMENT_BATCH       A payment batch. Can work in both directions.
93
 
    #                       Incoming payment batch transactions can't be
94
 
    #                       matched with payments, outgoing can.
95
 
    #                       Will be selected for matching.
96
 
    #   PAYMENT_TERMINAL    A payment with debit/credit card in a (web)shop
97
 
    #                       Invoice numbers and other hard criteria are most
98
 
    #                       likely missing.
99
 
    #                       Will be selected for matching
100
 
    #   PERIODIC_ORDER      An automated payment by the bank on your behalf.
101
 
    #                       Always outgoing.
102
 
    #                       Will be selected for matching.
103
 
    #
104
 
    #   Perhaps more will follow.
105
 
    #
106
 
    # When writing parsers, map other types with similar meaning to these to
107
 
    # prevent cluttering the API. For example: the Dutch ING Bank has a
108
 
    # transfer type Post Office, meaning a cash withdrawal from one of their
109
 
    # agencies. This can be mapped to BANK_TERMINAL without losing any
110
 
    # significance for OpenERP.
111
 
 
112
 
    BANK_COSTS = 'BC'
113
 
    BANK_TERMINAL = 'BT'
114
 
    CHECK = 'CK'
115
 
    DIRECT_DEBIT = 'DD'
116
 
    ORDER = 'DO'
117
 
    PAYMENT_BATCH = 'PB'
118
 
    PAYMENT_TERMINAL = 'PT' 
119
 
    PERIODIC_ORDER = 'PO'
120
 
 
121
 
    types = [
122
 
        BANK_COSTS, BANK_TERMINAL, CHECK, DIRECT_DEBIT, ORDER,
123
 
        PAYMENT_BATCH, PAYMENT_TERMINAL, PERIODIC_ORDER,
124
 
    ]
125
 
    type_map = {
126
 
        # This can be a translation map of type versus bank type. Each key is
127
 
        # the real transfer_type, each value is the mem_bank_transaction.type
128
 
    }
129
 
 
130
 
    def __init__(self, *args, **kwargs):
131
 
        super(mem_bank_transaction, self).__init__(*args, **kwargs)
132
 
        self.id = ''
133
 
        self.local_account = ''
134
 
        self.local_currency = ''
135
 
        self.execution_date = ''
136
 
        self.effective_date = ''
137
 
        self.remote_account = ''
138
 
        self.remote_owner = ''
139
 
        self.remote_currency = ''
140
 
        self.transferred_amount = ''
141
 
        self.transfer_type = ''
142
 
        self.reference = ''
143
 
        self.message = ''
144
 
        self.statement_id = ''
145
 
 
146
 
    def copy(self):
147
 
        '''
148
 
        Return a copy of self
149
 
        '''
150
 
        retval = mem_bank_transaction()
151
 
        for attr in self.__slots__:
152
 
            setattr(retval, attr, getattr(self, attr))
153
 
        return retval
154
 
 
155
 
    def _get_type(self):
156
 
        if self.transfer_type in self.type_map:
157
 
            return self.type_map[self.transfer_type]
158
 
        return self.transfer_type
159
 
 
160
 
    def _set_type(self, value):
161
 
        if value in self.types:
162
 
            self.transfer_type = value
163
 
        else:
164
 
            raise ValueError, _('Invalid value for transfer_type')
165
 
 
166
 
    type = property(_get_type, _set_type)
167
 
 
168
 
    def is_valid(self):
169
 
        '''
170
 
        Heuristic check: at least id, execution_date, remote_account and
171
 
        transferred_amount should be filled to create a valid transfer.
172
 
        '''
173
 
        return (self.execution_date and self.remote_account
174
 
                and self.transferred_amount and True) or False
175
 
 
176
 
class parser_type(type):
177
 
    '''
178
 
    Meta annex factory class for house keeping and collecting parsers.
179
 
    '''
180
 
    parsers = []
181
 
    parser_by_name = {}
182
 
    parser_by_code = {}
183
 
    parser_by_classname = {}
184
 
 
185
 
    def __new__(metacls, clsname, bases, clsdict):
186
 
        newcls = type.__new__(metacls, clsname, bases, clsdict)
187
 
        if 'name' in clsdict and newcls.name:
188
 
            metacls.parsers.append(newcls)
189
 
            metacls.parser_by_name[newcls.name] = newcls
190
 
            metacls.parser_by_code[newcls.code] = newcls
191
 
            metacls.parser_by_classname[clsname] = newcls
192
 
        return newcls
193
 
 
194
 
    @classmethod
195
 
    def get_parser_types(cls, sort='name'):
196
 
        '''
197
 
        Return the parser class names, optional in sort order.
198
 
        '''
199
 
        if sort == 'name':
200
 
            keys = cls.parser_by_name.keys()
201
 
            parsers = cls.parser_by_name
202
 
        else:
203
 
            keys = cls.parser_by_code.itervalues()
204
 
            parsers = cls.parser_by_code
205
 
        keys.sort()
206
 
        return [(parsers[x].code, parsers[x].name) for x in keys]
207
 
 
208
 
def create_parser(code):
209
 
    if code in parser_type.parser_by_code:
210
 
        return parser_type.parser_by_code[code]()
211
 
    return None
212
 
 
213
 
class parser(object):
214
 
    '''
215
 
    A parser delivers the interface for any parser object. Inherit from
216
 
    it to implement your own.
217
 
    You should at least implement the following at the class level:
218
 
        name -> the name of the parser, shown to the user and
219
 
                    translatable.
220
 
        code -> the identifier you care to give it. Not translatable
221
 
        doc  -> the description of the identifier. Shown to the user.
222
 
                    Translatable.
223
 
 
224
 
        parse -> the method for the actual parsing.
225
 
    '''
226
 
    __metaclass__ = parser_type
227
 
    name = None
228
 
    code = None
229
 
    doc = __doc__
230
 
 
231
 
    def parse(self, data):
232
 
        '''
233
 
        Parse data.
234
 
        
235
 
        data is a raw in memory file object. You have to split it in
236
 
        whatever chunks you see fit for parsing. It should return a list
237
 
        of mem_bank_statement objects. Every mem_bank_statement object
238
 
        should contain a list of mem_bank_transaction objects.
239
 
 
240
 
        For identification purposes, don't invent numbering of the transaction
241
 
        numbers or bank statements ids on your own - stick with those provided
242
 
        by your bank. Doing so enables the users to re-load old transaction
243
 
        files without creating multiple identical bank statements.
244
 
        '''
245
 
        raise NotImplementedError(
246
 
            _('This is a stub. Please implement your own.')
247
 
        )
248
 
 
249
 
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: