← Back to team overview

banking-addons-team team mailing list archive

[Merge] lp:~endiansolutions/banking-addons/ab61-nl_rabo into lp:banking-addons

 

Erwin van der Ploeg (Endian Solutions) has proposed merging lp:~endiansolutions/banking-addons/ab61-nl_rabo into lp:banking-addons.

Requested reviews:
  Banking Addons Team (banking-addons-team)

For more details, see:
https://code.launchpad.net/~endiansolutions/banking-addons/ab61-nl_rabo/+merge/141149
-- 
https://code.launchpad.net/~endiansolutions/banking-addons/ab61-nl_rabo/+merge/141149
Your team Banking Addons Team is requested to review the proposed merge of lp:~endiansolutions/banking-addons/ab61-nl_rabo into lp:banking-addons.
=== added directory 'account_banking_nl_rabobank'
=== added file 'account_banking_nl_rabobank/__init__.py'
--- account_banking_nl_rabobank/__init__.py	1970-01-01 00:00:00 +0000
+++ account_banking_nl_rabobank/__init__.py	2012-12-22 11:43:21 +0000
@@ -0,0 +1,24 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    Copyright (C) 2011 credativ Ltd (<http://www.credativ.co.uk>).
+#    All Rights Reserved
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import mt940
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

=== added file 'account_banking_nl_rabobank/__openerp__.py'
--- account_banking_nl_rabobank/__openerp__.py	1970-01-01 00:00:00 +0000
+++ account_banking_nl_rabobank/__openerp__.py	2012-12-22 11:43:21 +0000
@@ -0,0 +1,21 @@
+# -*- encoding: utf-8 -*-
+
+{
+    'name': 'Rabobank bankafschrift import',
+    'version': '0.1',
+    'license': 'GPL-3',
+    'author': 'Endian Solutions B.V.',
+    'website': 'http://www.endiansolutions.nl',
+    'category': 'Account Banking',
+    'depends': ['account_banking'],
+    'init_xml': [],
+    'update_xml': [],
+    'demo_xml': [],
+    'description': '''
+    Deze module importeerd het Rabobank MT940 bankafschriftformaat.
+
+    Deze module is een import filter op de module account_banking.
+    ''',
+    'active': False,
+    'installable': True,
+}

=== added directory 'account_banking_nl_rabobank/i18n'
=== added file 'account_banking_nl_rabobank/i18n/nl.po'
--- account_banking_nl_rabobank/i18n/nl.po	1970-01-01 00:00:00 +0000
+++ account_banking_nl_rabobank/i18n/nl.po	2012-12-22 11:43:21 +0000
@@ -0,0 +1,36 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+# 	* account_banking_nl_rabobank
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 6.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-12-16 12:07+0000\n"
+"PO-Revision-Date: 2012-12-16 13:08+0100\n"
+"Last-Translator: Erwin van der Ploeg | Endian Solutions "
+"<erwin@xxxxxxxxxxxxxxxxxx>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: \n"
+"X-Generator: Poedit 1.5.4\n"
+
+#. module: account_banking_nl_rabobank
+#: code:addons/account_banking_nl_rabobank/mt940.py:142
+#, python-format
+msgid "Import error"
+msgstr "Import fout"
+
+#. module: account_banking_nl_rabobank
+#: code:addons/account_banking_nl_rabobank/mt940.py:150
+#, python-format
+msgid "Accounting banking Interface is used"
+msgstr "Accounting banking Interface is gebruikt"
+
+#. module: account_banking_nl_rabobank
+#: code:addons/account_banking_nl_rabobank/mt940.py:148
+#, python-format
+msgid "Swift MT940 statement export"
+msgstr "Rabobank MT940 bankafschrift import"

=== added file 'account_banking_nl_rabobank/mt940.py'
--- account_banking_nl_rabobank/mt940.py	1970-01-01 00:00:00 +0000
+++ account_banking_nl_rabobank/mt940.py	2012-12-22 11:43:21 +0000
@@ -0,0 +1,182 @@
+# -*- encoding: utf-8 -*-
+import re
+import osv
+import logging
+
+from account_banking.parsers import models
+from tools.translate import _
+from mt940_parser import MT940Parser
+
+bt = models.mem_bank_transaction
+logger = logging.getLogger('mt940')
+
+
+def record2float(record, value):
+    if record['creditmarker'][-1] == 'C':
+        return float(record[value])
+    return -float(record[value])
+
+
+class transaction(models.mem_bank_transaction):
+
+    mapping = {
+        'execution_date': 'valuedate',
+        'effective_date': 'valuedate',
+        'local_currency': 'currency',
+        'transfer_type': 'bookingcode',
+        'remote_account': 'custrefno',
+        'message': 'infoline1',
+    }
+
+    type_map = {
+        'NTRF': bt.ORDER,
+        'NMSC': bt.ORDER,
+        'NPAY': bt.PAYMENT_BATCH,
+        'NCHK': bt.CHECK,
+    }
+
+    def __init__(self, record, *args, **kwargs):
+        '''
+        Transaction creation
+        '''
+        super(transaction, self).__init__(*args, **kwargs)
+        for key, value in self.mapping.iteritems():
+            if value in record:
+                setattr(self, key, record[value])
+
+        self.transferred_amount = record2float(record, 'amount')
+        self.reference = None
+
+        # Set the transfer type based on the bookingcode
+        if record.get('bookingcode', 'ignore') in self.type_map:
+            self.transfer_type = self.type_map[record['bookingcode']]
+        else:
+            # Default to the generic order, so it will be eligible for matching
+            self.transfer_type = bt.ORDER
+
+        if not self.is_valid():
+            logger.info("Invalid: %s", record)
+
+    def is_valid(self):
+        '''
+        We don't have remote_account so override base
+        '''
+        return (self.execution_date
+                and self.transferred_amount and True) or False
+
+
+class statement(models.mem_bank_statement):
+    '''
+    Bank statement imported data
+    '''
+
+    def __init__(self, *args, **kwargs):
+        """Set defaults to fill from first statement."""
+        super(statement, self).__init__(*args, **kwargs)
+        self.local_account = None
+        self.start_balance = 0
+
+
+    def import_record(self, record):
+        def _transmission_number():
+            self.id = record['transref']
+
+        def _account_number():
+            acc_num = record['accnum'].replace('.', '').zfill(10)
+            self.local_account = acc_num
+            self.id = record['accnum']
+
+        def _statement_number():
+            pass
+
+        def _opening_balance():
+            if not self.start_balance:
+                self.start_balance = record2float(record, 'startingbalance')
+                self.local_currency = record['currencycode']
+
+        def _closing_balance():
+            if record2float(record, 'endingbalance'):
+                self.end_balance = record2float(record, 'endingbalance')
+                self.date = record['bookingdate']
+
+        def _transaction_new():
+            self.transactions.append(transaction(record))
+
+        def _transaction_info():
+            self.transaction_info(record)
+
+        def _not_used():
+            logger.info("Didn't use record: %s", record)
+
+        rectypes = {
+            '20': _transmission_number,
+            '25': _account_number,
+            '28': _statement_number,
+            '28C': _statement_number,
+            '60F': _opening_balance,
+            '62F': _closing_balance,
+           #'64': _forward_available,
+           #'62M': _interim_balance,
+            '61': _transaction_new,
+            '86': _transaction_info,
+            }
+
+        rectypes.get(record.get('recordid'), _not_used)()
+
+    def transaction_info(self, record):
+        '''
+        Add extra information to transaction
+        '''
+        # Additional information for previous transaction
+        if len(self.transactions) < 1:
+            logger.info("Received additional information for non existent transaction:")
+            logger.info(record)
+        else:
+            transaction = self.transactions[-1]
+            if not transaction.reference:
+                transaction.reference = record['infoline1']
+            transaction.message += '{0}\n'.format(record['infoline1'])
+
+
+def raise_error(message, line):
+    raise osv.osv.except_osv(_('Import error'),
+        'Error in import:%s\n\n%s' % (message, line))
+
+
+class parser_mt940(models.parser):
+    code = 'MT940'
+    name = _('Swift MT940 statement export')
+    country_code = 'NL'
+    doc = _('''Accounting banking Interface is used''')
+
+    def parse(self, cr, data):
+        result = []
+        parser = MT940Parser()
+        # Split into statements
+        statements = [st for st in re.split('[\r\n]*(?=:20:)', data)]
+        # Split by records
+        statement_list = [re.split('[\r\n ]*(?=:\d\d[\w]?:)', st)
+                             for st in statements]
+        stmnt = None
+        current_account = None
+        for statement_lines in statement_list:
+            records = [parser.parse_record(record) for
+                                                     record in statement_lines]
+            if len(records) > 2 and not (
+                                    current_account == records[1]['accnum']):
+                current_account = records[1]['accnum']
+                if stmnt:
+                    if stmnt.is_valid() and stmnt.transactions:
+                        result.append(stmnt)
+                    else:
+                        logger.info("Invalid Statement:")
+                        logger.info(records[0])
+                stmnt = statement()
+
+            [stmnt.import_record(r) for r in records if r is not None]
+        # Last statement check
+        if stmnt.is_valid() and stmnt.transactions:
+            result.append(stmnt)
+        return result
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

=== added file 'account_banking_nl_rabobank/mt940_parser.py'
--- account_banking_nl_rabobank/mt940_parser.py	1970-01-01 00:00:00 +0000
+++ account_banking_nl_rabobank/mt940_parser.py	2012-12-22 11:43:21 +0000
@@ -0,0 +1,139 @@
+#!/usr/bin/env python
+# -*- encoding: utf-8 -*-
+
+"""
+Based on fi_patu's parser
+"""
+import re
+
+from datetime import datetime
+
+
+class MT940Parser(object):
+
+    def __init__(self):
+        recparse = dict()
+        # patterns = {'ebcdic': "\w/\?:\(\).,'+{} -"}
+
+        # MT940 header
+        # recparse["940"] = ":(?P<recordid>940):"
+        recparse["20"] = ":(?P<recordid>20):(?P<transref>.{1,14})"
+        recparse["25"] = ":(?P<recordid>25):(?P<accnum>.*\d{2,15})"
+        recparse["28"] = ":(?P<recordid>28?):(?P<statementnr>.{1,8})"
+
+        # Opening balance 60F
+        recparse["60F"] = ":(?P<recordid>60F):(?P<creditmarker>[CD])" \
+                + "(?P<prevstmtdate>\d{6})(?P<currencycode>.{3})" \
+                + "(?P<startingbalance>[\d,]{1,15})"
+
+        # Transaction
+        recparse["61"] = """\
+:(?P<recordid>61):\
+(?P<valuedate>\d{6})\
+(?P<creditmarker>R?[CD])\
+(?P<amount>[\d,]{1,15})\
+(?P<bookingcode>[A-Z0-9]{4})\
+(?P<custrefno>[A-Z0-9]{1,16})\
+(?P<furtherinfo>.{1-34})?"""
+
+        # Further info
+        recparse["86"] = ":(?P<recordid>86):" \
+                + "(?P<infoline1>.{1,80})?" \
+                + "(?:\n(?P<infoline2>.{1,80}))?" \
+                + "(?:\n(?P<infoline3>.{1,80}))?" \
+                + "(?:\n(?P<infoline4>.{1,80}))?" \
+                + "(?:\n(?P<infoline5>.{1,80}))?"
+
+        # Forward available balance (64) /  Closing balance (62F) / Interim balance (62M)
+        recparse["64"] = ":(?P<recordid>64|62[FM]):" \
+                + "(?P<creditmarker>[CD])" \
+                + "(?P<bookingdate>\d{6})(?P<currencycode>.{3})" \
+                + "(?P<endingbalance>[\d,]{1,15})"
+
+        for record in recparse:
+            recparse[record] = re.compile(recparse[record])
+        self.recparse = recparse
+
+    def parse_record(self, line):
+        """
+        Parse record using regexps and apply post processing
+        """
+        for matcher in self.recparse:
+            matchobj = self.recparse[matcher].match(line)
+            if matchobj:
+                break
+        if not matchobj:
+            print " **** failed to match line '%s'" % (line)
+            return
+        # Strip strings
+        matchdict = matchobj.groupdict()
+
+        # Remove members set to None
+        matchdict = dict([(k, v) for k, v in matchdict.iteritems() if v])
+
+        matchkeys = set(matchdict.keys())
+        needstrip = set(["transref", "accnum", "statementnr", "custrefno",
+            "bankref", "furtherinfo", "infoline1", "infoline2", "infoline3",
+            "infoline4", "infoline5", "startingbalance", "endingbalance"])
+        for field in matchkeys & needstrip:
+            matchdict[field] = matchdict[field].strip()
+
+        # Convert to float. Comma is decimal separator
+        needsfloat = set(["startingbalance", "endingbalance", "amount"])
+        for field in matchkeys & needsfloat:
+            matchdict[field] = float(matchdict[field].replace(',', '.'))
+
+        # Convert date fields
+        needdate = set(["prevstmtdate", "valuedate", "bookingdate"])
+        for field in matchkeys & needdate:
+            datestring = matchdict[field]
+
+            post_check = False
+            if len(datestring) == 4 and field == "bookingdate" and\
+                matchdict.has_key("valuedate"):
+                # Get year from valuedate
+                datestring = matchdict['valuedate'].strftime('%y') + datestring
+                post_check = True
+            try:
+                matchdict[field] = datetime.strptime(datestring, '%y%m%d')
+                if post_check and matchdict[field] > matchdict["valuedate"]:
+                    matchdict[field] = matchdict[field].replace(
+                                            year=matchdict[field].year - 1)
+            except ValueError:
+                matchdict[field] = None
+
+        return matchdict
+
+    def parse(self, cr, data):
+        records = []
+        # Some records are multiline
+        for line in data:
+            if len(line) <= 1:
+                continue
+            if line[0] == ':' and len(line) > 1:
+                records.append(line)
+            else:
+                records[-1] = '\n'.join([records[-1], line])
+
+        output = []
+        for rec in records:
+            output.append(self.parse_record(rec))
+
+        return output
+
+
+def parse_file(filename):
+    inputfile = open(filename, "r")
+    res = MT940Parser().parse(inputfile.readlines())
+    print str(res)
+
+
+def main():
+    """The main function, currently just calls a dummy filename
+
+    :returns: description
+    """
+    parse_file("mut.swi")
+
+if __name__ == '__main__':
+    main()


Follow ups