mirror of
https://github.com/OCA/bank-payment.git
synced 2025-02-02 10:37:31 +02:00
[FIX] Move unported import parsers to bank-statement-import project.
This commit is contained in:
@@ -1 +0,0 @@
|
|||||||
import camt
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Copyright (C) 2013 Therp BV (<http://therp.nl>)
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
{
|
|
||||||
'name': 'CAMT Format Bank Statements Import',
|
|
||||||
'version': '0.1',
|
|
||||||
'license': 'AGPL-3',
|
|
||||||
'author': 'Therp BV',
|
|
||||||
'website': 'https://launchpad.net/banking-addons',
|
|
||||||
'category': 'Banking addons',
|
|
||||||
'depends': ['account_banking'],
|
|
||||||
'description': '''
|
|
||||||
Module to import SEPA CAMT.053 Format bank statement files. Based
|
|
||||||
on the Banking addons framework.
|
|
||||||
''',
|
|
||||||
'installable': False,
|
|
||||||
}
|
|
||||||
@@ -1,285 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Copyright (C) 2013 Therp BV (<http://therp.nl>)
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
from openerp.osv.orm import except_orm
|
|
||||||
from openerp.addons.account_banking.parsers import models
|
|
||||||
from openerp.addons.account_banking.parsers.convert import str2date
|
|
||||||
|
|
||||||
bt = models.mem_bank_transaction
|
|
||||||
|
|
||||||
|
|
||||||
class transaction(models.mem_bank_transaction):
|
|
||||||
|
|
||||||
def __init__(self, values, *args, **kwargs):
|
|
||||||
super(transaction, self).__init__(*args, **kwargs)
|
|
||||||
for attr in values:
|
|
||||||
setattr(self, attr, values[attr])
|
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
return not self.error_message
|
|
||||||
|
|
||||||
|
|
||||||
class parser(models.parser):
|
|
||||||
code = 'CAMT'
|
|
||||||
country_code = 'NL'
|
|
||||||
name = 'Generic CAMT Format'
|
|
||||||
doc = '''\
|
|
||||||
CAMT Format parser
|
|
||||||
'''
|
|
||||||
|
|
||||||
def tag(self, node):
|
|
||||||
"""
|
|
||||||
Return the tag of a node, stripped from its namespace
|
|
||||||
"""
|
|
||||||
return node.tag[len(self.ns):]
|
|
||||||
|
|
||||||
def assert_tag(self, node, expected):
|
|
||||||
"""
|
|
||||||
Get node's stripped tag and compare with expected
|
|
||||||
"""
|
|
||||||
assert self.tag(node) == expected, (
|
|
||||||
"Expected tag '%s', got '%s' instead" %
|
|
||||||
(self.tag(node), expected))
|
|
||||||
|
|
||||||
def xpath(self, node, expr):
|
|
||||||
"""
|
|
||||||
Wrap namespaces argument into call to Element.xpath():
|
|
||||||
|
|
||||||
self.xpath(node, './ns:Acct/ns:Id')
|
|
||||||
"""
|
|
||||||
return node.xpath(expr, namespaces={'ns': self.ns[1:-1]})
|
|
||||||
|
|
||||||
def find(self, node, expr):
|
|
||||||
"""
|
|
||||||
Like xpath(), but return first result if any or else False
|
|
||||||
|
|
||||||
Return None to test nodes for being truesy
|
|
||||||
"""
|
|
||||||
result = node.xpath(expr, namespaces={'ns': self.ns[1:-1]})
|
|
||||||
if result:
|
|
||||||
return result[0]
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_balance_type_node(self, node, balance_type):
|
|
||||||
"""
|
|
||||||
:param node: BkToCstmrStmt/Stmt/Bal node
|
|
||||||
:param balance type: one of 'OPBD', 'PRCD', 'ITBD', 'CLBD'
|
|
||||||
"""
|
|
||||||
code_expr = ('./ns:Bal/ns:Tp/ns:CdOrPrtry/ns:Cd[text()="%s"]/../../..'
|
|
||||||
% balance_type)
|
|
||||||
return self.xpath(node, code_expr)
|
|
||||||
|
|
||||||
def parse_amount(self, node):
|
|
||||||
"""
|
|
||||||
Parse an element that contains both Amount and CreditDebitIndicator
|
|
||||||
|
|
||||||
:return: signed amount
|
|
||||||
:returntype: float
|
|
||||||
"""
|
|
||||||
sign = -1 if node.find(self.ns + 'CdtDbtInd').text == 'DBIT' else 1
|
|
||||||
return sign * float(node.find(self.ns + 'Amt').text)
|
|
||||||
|
|
||||||
def get_start_balance(self, node):
|
|
||||||
"""
|
|
||||||
Find the (only) balance node with code OpeningBalance, or
|
|
||||||
the only one with code 'PreviousClosingBalance'
|
|
||||||
or the first balance node with code InterimBalance in
|
|
||||||
the case of preceeding pagination.
|
|
||||||
|
|
||||||
:param node: BkToCstmrStmt/Stmt/Bal node
|
|
||||||
"""
|
|
||||||
nodes = (
|
|
||||||
self.get_balance_type_node(node, 'OPBD') or
|
|
||||||
self.get_balance_type_node(node, 'PRCD') or
|
|
||||||
self.get_balance_type_node(node, 'ITBD'))
|
|
||||||
return self.parse_amount(nodes[0])
|
|
||||||
|
|
||||||
def get_end_balance(self, node):
|
|
||||||
"""
|
|
||||||
Find the (only) balance node with code ClosingBalance, or
|
|
||||||
the second (and last) balance node with code InterimBalance in
|
|
||||||
the case of continued pagination.
|
|
||||||
|
|
||||||
:param node: BkToCstmrStmt/Stmt/Bal node
|
|
||||||
"""
|
|
||||||
nodes = (
|
|
||||||
self.get_balance_type_node(node, 'CLBD') or
|
|
||||||
self.get_balance_type_node(node, 'ITBD'))
|
|
||||||
return self.parse_amount(nodes[-1])
|
|
||||||
|
|
||||||
def parse_Stmt(self, cr, node):
|
|
||||||
"""
|
|
||||||
Parse a single Stmt node.
|
|
||||||
|
|
||||||
Be sure to craft a unique, but short enough statement identifier,
|
|
||||||
as it is used as the basis of the generated move lines' names
|
|
||||||
which overflow when using the full IBAN and CAMT statement id.
|
|
||||||
"""
|
|
||||||
statement = models.mem_bank_statement()
|
|
||||||
statement.local_account = (
|
|
||||||
self.xpath(node, './ns:Acct/ns:Id/ns:IBAN')[0].text
|
|
||||||
if self.xpath(node, './ns:Acct/ns:Id/ns:IBAN')
|
|
||||||
else self.xpath(node, './ns:Acct/ns:Id/ns:Othr/ns:Id')[0].text)
|
|
||||||
|
|
||||||
identifier = node.find(self.ns + 'Id').text
|
|
||||||
if identifier.upper().startswith('CAMT053'):
|
|
||||||
identifier = identifier[7:]
|
|
||||||
statement.id = self.get_unique_statement_id(
|
|
||||||
cr, "%s-%s" % (
|
|
||||||
self.get_unique_account_identifier(
|
|
||||||
cr, statement.local_account),
|
|
||||||
identifier)
|
|
||||||
)
|
|
||||||
|
|
||||||
statement.local_currency = self.xpath(node, './ns:Acct/ns:Ccy')[0].text
|
|
||||||
statement.start_balance = self.get_start_balance(node)
|
|
||||||
statement.end_balance = self.get_end_balance(node)
|
|
||||||
number = 0
|
|
||||||
for Ntry in self.xpath(node, './ns:Ntry'):
|
|
||||||
transaction_detail = self.parse_Ntry(Ntry)
|
|
||||||
if number == 0:
|
|
||||||
# Take the statement date from the first transaction
|
|
||||||
statement.date = str2date(
|
|
||||||
transaction_detail['execution_date'], "%Y-%m-%d")
|
|
||||||
number += 1
|
|
||||||
transaction_detail['id'] = str(number).zfill(4)
|
|
||||||
statement.transactions.append(
|
|
||||||
transaction(transaction_detail))
|
|
||||||
return statement
|
|
||||||
|
|
||||||
def get_transfer_type(self, node):
|
|
||||||
"""
|
|
||||||
Map entry descriptions to transfer types. To extend with
|
|
||||||
proper mapping from BkTxCd/Domn/Cd/Fmly/Cd to transfer types
|
|
||||||
if we can get our hands on real life samples.
|
|
||||||
|
|
||||||
For now, leave as a hook for bank specific overrides to map
|
|
||||||
properietary codes from BkTxCd/Prtry/Cd.
|
|
||||||
|
|
||||||
:param node: Ntry node
|
|
||||||
"""
|
|
||||||
return bt.ORDER
|
|
||||||
|
|
||||||
def parse_Ntry(self, node):
|
|
||||||
"""
|
|
||||||
:param node: Ntry node
|
|
||||||
"""
|
|
||||||
entry_details = {
|
|
||||||
'execution_date': self.xpath(node, './ns:BookgDt/ns:Dt')[0].text,
|
|
||||||
'value_date': self.xpath(node, './ns:ValDt/ns:Dt')[0].text,
|
|
||||||
'transfer_type': self.get_transfer_type(node),
|
|
||||||
'transferred_amount': self.parse_amount(node)
|
|
||||||
}
|
|
||||||
TxDtls = self.xpath(node, './ns:NtryDtls/ns:TxDtls')
|
|
||||||
if len(TxDtls) == 1:
|
|
||||||
vals = self.parse_TxDtls(TxDtls[0], entry_details)
|
|
||||||
else:
|
|
||||||
vals = entry_details
|
|
||||||
return vals
|
|
||||||
|
|
||||||
def get_party_values(self, TxDtls):
|
|
||||||
"""
|
|
||||||
Determine to get either the debtor or creditor party node
|
|
||||||
and extract the available data from it
|
|
||||||
"""
|
|
||||||
vals = {}
|
|
||||||
party_type = self.find(
|
|
||||||
TxDtls, '../../ns:CdtDbtInd').text == 'CRDT' and 'Dbtr' or 'Cdtr'
|
|
||||||
party_node = self.find(TxDtls, './ns:RltdPties/ns:%s' % party_type)
|
|
||||||
account_node = self.find(
|
|
||||||
TxDtls, './ns:RltdPties/ns:%sAcct/ns:Id' % party_type)
|
|
||||||
bic_node = self.find(
|
|
||||||
TxDtls,
|
|
||||||
'./ns:RltdAgts/ns:%sAgt/ns:FinInstnId/ns:BIC' % party_type)
|
|
||||||
if party_node is not None:
|
|
||||||
name_node = self.find(party_node, './ns:Nm')
|
|
||||||
vals['remote_owner'] = (
|
|
||||||
name_node.text if name_node is not None else False)
|
|
||||||
country_node = self.find(party_node, './ns:PstlAdr/ns:Ctry')
|
|
||||||
vals['remote_owner_country'] = (
|
|
||||||
country_node.text if country_node is not None else False)
|
|
||||||
address_node = self.find(party_node, './ns:PstlAdr/ns:AdrLine')
|
|
||||||
if address_node is not None:
|
|
||||||
vals['remote_owner_address'] = [address_node.text]
|
|
||||||
if account_node is not None:
|
|
||||||
iban_node = self.find(account_node, './ns:IBAN')
|
|
||||||
if iban_node is not None:
|
|
||||||
vals['remote_account'] = iban_node.text
|
|
||||||
if bic_node is not None:
|
|
||||||
vals['remote_bank_bic'] = bic_node.text
|
|
||||||
else:
|
|
||||||
domestic_node = self.find(account_node, './ns:Othr/ns:Id')
|
|
||||||
vals['remote_account'] = (
|
|
||||||
domestic_node.text if domestic_node is not None else False)
|
|
||||||
return vals
|
|
||||||
|
|
||||||
def parse_TxDtls(self, TxDtls, entry_values):
|
|
||||||
"""
|
|
||||||
Parse a single TxDtls node
|
|
||||||
"""
|
|
||||||
vals = dict(entry_values)
|
|
||||||
unstructured = self.xpath(TxDtls, './ns:RmtInf/ns:Ustrd')
|
|
||||||
if unstructured:
|
|
||||||
vals['message'] = ' '.join([x.text for x in unstructured])
|
|
||||||
structured = self.find(
|
|
||||||
TxDtls, './ns:RmtInf/ns:Strd/ns:CdtrRefInf/ns:Ref')
|
|
||||||
if structured is None or not structured.text:
|
|
||||||
structured = self.find(TxDtls, './ns:Refs/ns:EndToEndId')
|
|
||||||
if structured is not None:
|
|
||||||
vals['reference'] = structured.text
|
|
||||||
else:
|
|
||||||
if vals.get('message'):
|
|
||||||
vals['reference'] = vals['message']
|
|
||||||
vals.update(self.get_party_values(TxDtls))
|
|
||||||
return vals
|
|
||||||
|
|
||||||
def check_version(self):
|
|
||||||
"""
|
|
||||||
Sanity check the document's namespace
|
|
||||||
"""
|
|
||||||
if not self.ns.startswith('{urn:iso:std:iso:20022:tech:xsd:camt.')\
|
|
||||||
and not self.ns.startswith('{ISO:camt.'):
|
|
||||||
raise except_orm(
|
|
||||||
"Error",
|
|
||||||
"This does not seem to be a CAMT format bank statement.")
|
|
||||||
|
|
||||||
if not self.ns.startswith('{urn:iso:std:iso:20022:tech:xsd:camt.053.')\
|
|
||||||
and not self.ns.startswith('{ISO:camt.053'):
|
|
||||||
raise except_orm(
|
|
||||||
"Error",
|
|
||||||
"Only CAMT.053 is supported at the moment.")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def parse(self, cr, data):
|
|
||||||
"""
|
|
||||||
Parse a CAMT053 XML file
|
|
||||||
"""
|
|
||||||
root = etree.fromstring(data)
|
|
||||||
self.ns = root.tag[:root.tag.index("}") + 1]
|
|
||||||
self.check_version()
|
|
||||||
self.assert_tag(root[0][0], 'GrpHdr')
|
|
||||||
statements = []
|
|
||||||
for node in root[0][1:]:
|
|
||||||
statement = self.parse_Stmt(cr, node)
|
|
||||||
if len(statement.transactions):
|
|
||||||
statements.append(statement)
|
|
||||||
return statements
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Copyright (C) 2010 Sami Haahtinen (<http://ressukka.net>).
|
|
||||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
|
||||||
# All Rights Reserved
|
|
||||||
#
|
|
||||||
# WARNING: This program as such is intended to be used by professional
|
|
||||||
# programmers who take the whole responsability of assessing all potential
|
|
||||||
# consequences resulting from its eventual inadequacies and bugs
|
|
||||||
# End users who are looking for a ready-to-use solution with commercial
|
|
||||||
# garantees and support are strongly adviced to contract EduSense BV
|
|
||||||
#
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
from . import patu
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Copyright (C) 2010 Sami Haahtinen (<http://ressukka.net>).
|
|
||||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
|
||||||
# All Rights Reserved
|
|
||||||
#
|
|
||||||
# WARNING: This program as such is intended to be used by professional
|
|
||||||
# programmers who take the whole responsability of assessing all potential
|
|
||||||
# consequences resulting from its eventual inadequacies and bugs
|
|
||||||
# End users who are looking for a ready-to-use solution with commercial
|
|
||||||
# garantees and support are strongly adviced to contract EduSense BV
|
|
||||||
#
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
{
|
|
||||||
'name': 'Account Banking PATU module',
|
|
||||||
'version': '0.62',
|
|
||||||
'license': 'AGPL-3',
|
|
||||||
'author': 'Sami Haahtinen',
|
|
||||||
'website': 'http://ressukka.net',
|
|
||||||
'category': 'Account Banking',
|
|
||||||
'depends': ['account_banking'],
|
|
||||||
'description': '''
|
|
||||||
Module to import Finnish PATU format transation files.
|
|
||||||
|
|
||||||
This modules contains no logic, just an import filter for account_banking.
|
|
||||||
''',
|
|
||||||
'active': False,
|
|
||||||
'installable': False,
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# Translation of OpenERP Server.
|
|
||||||
# This file contains the translation of the following modules:
|
|
||||||
# * account_banking_fi_patu
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: OpenERP Server 7.0\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2013-10-25 15:53+0000\n"
|
|
||||||
"PO-Revision-Date: 2013-10-25 15:53+0000\n"
|
|
||||||
"Last-Translator: <>\n"
|
|
||||||
"Language-Team: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: \n"
|
|
||||||
"Plural-Forms: \n"
|
|
||||||
|
|
||||||
#. module: account_banking_fi_patu
|
|
||||||
#: code:addons/account_banking_fi_patu/patu.py:115
|
|
||||||
#, python-format
|
|
||||||
msgid "PATU statement sheet"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_fi_patu
|
|
||||||
#: code:addons/account_banking_fi_patu/patu.py:116
|
|
||||||
#, python-format
|
|
||||||
msgid "PATU statement format defines one or more statements in each file. This parser\n"
|
|
||||||
"will parse all statements in a file and import them to OpenERP\n"
|
|
||||||
""
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# Dutch translation for banking-addons
|
|
||||||
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
|
|
||||||
# This file is distributed under the same license as the banking-addons package.
|
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: banking-addons\n"
|
|
||||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
|
||||||
"POT-Creation-Date: 2013-10-25 15:53+0000\n"
|
|
||||||
"PO-Revision-Date: 2013-12-03 11:08+0000\n"
|
|
||||||
"Last-Translator: Erwin van der Ploeg (BAS Solutions) <Unknown>\n"
|
|
||||||
"Language-Team: Dutch <nl@li.org>\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"X-Launchpad-Export-Date: 2014-05-31 06:02+0000\n"
|
|
||||||
"X-Generator: Launchpad (build 17031)\n"
|
|
||||||
|
|
||||||
#. module: account_banking_fi_patu
|
|
||||||
#: code:addons/account_banking_fi_patu/patu.py:115
|
|
||||||
#, python-format
|
|
||||||
msgid "PATU statement sheet"
|
|
||||||
msgstr "PATU bankafschrift"
|
|
||||||
|
|
||||||
#. module: account_banking_fi_patu
|
|
||||||
#: code:addons/account_banking_fi_patu/patu.py:116
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"PATU statement format defines one or more statements in each file. This "
|
|
||||||
"parser\n"
|
|
||||||
"will parse all statements in a file and import them to OpenERP\n"
|
|
||||||
msgstr ""
|
|
||||||
"PATU afschrift formaat definieert een of meerdere afschriften in ieder "
|
|
||||||
"bestand. deze parser\n"
|
|
||||||
"zal alle afschriften verwerken in het bestand en deze importeren in "
|
|
||||||
"OpenERP.\n"
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# Brazilian Portuguese translation for banking-addons
|
|
||||||
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
|
|
||||||
# This file is distributed under the same license as the banking-addons package.
|
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: banking-addons\n"
|
|
||||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
|
||||||
"POT-Creation-Date: 2013-10-25 15:53+0000\n"
|
|
||||||
"PO-Revision-Date: 2013-12-25 12:32+0000\n"
|
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
||||||
"Language-Team: Brazilian Portuguese <pt_BR@li.org>\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"X-Launchpad-Export-Date: 2014-05-31 06:02+0000\n"
|
|
||||||
"X-Generator: Launchpad (build 17031)\n"
|
|
||||||
|
|
||||||
#. module: account_banking_fi_patu
|
|
||||||
#: code:addons/account_banking_fi_patu/patu.py:115
|
|
||||||
#, python-format
|
|
||||||
msgid "PATU statement sheet"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_fi_patu
|
|
||||||
#: code:addons/account_banking_fi_patu/patu.py:116
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"PATU statement format defines one or more statements in each file. This "
|
|
||||||
"parser\n"
|
|
||||||
"will parse all statements in a file and import them to OpenERP\n"
|
|
||||||
msgstr ""
|
|
||||||
@@ -1,250 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# encoding: utf-8
|
|
||||||
"""Parser for PATU format files"""
|
|
||||||
import re
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
|
|
||||||
def fixchars(line):
|
|
||||||
"""Fix the characters mangled in the input
|
|
||||||
|
|
||||||
:param line: Line to rewrite
|
|
||||||
|
|
||||||
:returns: string, fixed line
|
|
||||||
"""
|
|
||||||
# Fix the umlauts int the input
|
|
||||||
line = line.replace("{", u"ä")
|
|
||||||
line = line.replace("}", u"ö")
|
|
||||||
# XXX: There are a whole bunch of these, adding them later
|
|
||||||
return line
|
|
||||||
|
|
||||||
|
|
||||||
class PatuParser(object):
|
|
||||||
"""Parse PATU lines in to structs"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
""" Initialize PATU parser """
|
|
||||||
|
|
||||||
recparse = dict()
|
|
||||||
recparse["00"] = (
|
|
||||||
"T(?P<recordid>00)(?P<record_len>\d{3})"
|
|
||||||
"(?P<version>\d{3})(?P<accountnr>\d{14})"
|
|
||||||
"(?P<statementnr>\d{3})(?P<startdate>\d{6})"
|
|
||||||
"(?P<enddate>\d{6})"
|
|
||||||
"(?P<creationdate>\d{6})(?P<creationtime>\d{4})"
|
|
||||||
"(?P<customerid>.{17})(?P<balancedate>\d{6})"
|
|
||||||
"(?P<startingbalance>.{19})"
|
|
||||||
"(?P<itemcount>\d{6})(?P<currency>.{3})"
|
|
||||||
"(?P<accountname>.{30})"
|
|
||||||
"(?P<accountlimit>\d{18})(?P<accountowner>.{35})"
|
|
||||||
"(?P<bankcontact1>.{40})(?P<bankcontact2>.{40})"
|
|
||||||
"(?P<bankcontact3>.{30})(?P<ibanswift>.{30})"
|
|
||||||
)
|
|
||||||
recparse["10"] = (
|
|
||||||
"T(?P<recordid>[18]0)(?P<record_len>\d{3})"
|
|
||||||
"(?P<eventid>\d{6})"
|
|
||||||
"(?P<archivalnr>.{18})(?P<recorddate>\d{6})"
|
|
||||||
"(?P<valuedate>\d{6})"
|
|
||||||
"(?P<paymentdate>\d{6})(?P<eventtype>\d)"
|
|
||||||
"(?P<eventcode>.{3})(?P<eventdesc>.{35})"
|
|
||||||
"(?P<amount>.{19})(?P<receiptcode>.)(?P<creationmethod>.)"
|
|
||||||
"(?P<recipientname>.{35})(?P<recipientsource>.)"
|
|
||||||
"(?P<recipientaccount>.{14})(?P<recipientaccountchanged>.)"
|
|
||||||
"(?P<refnr>.{20})"
|
|
||||||
"(?P<formnr>.{8})(?P<eventlevel>.)"
|
|
||||||
)
|
|
||||||
recparse["11"] = (
|
|
||||||
"T(?P<recordid>[18]1)(?P<record_len>\d{3})"
|
|
||||||
"(?P<infotype>.{2})"
|
|
||||||
"(?:(?# Match specific info)"
|
|
||||||
"(?<=00)(?P<message>.{35})+"
|
|
||||||
"|"
|
|
||||||
"(?<=01)(?P<transactioncount>\d{8})"
|
|
||||||
"|"
|
|
||||||
"(?<=02)(?P<customerid>.{10})\s(?P<invoicenr>.{15})\s"
|
|
||||||
"(?P<invoicedate>\d{6})"
|
|
||||||
"|"
|
|
||||||
"(?<=03)(?P<cardnumber>.{19})\s(?P<storereference>.{14})"
|
|
||||||
"|"
|
|
||||||
"(?<=04)(?P<origarchiveid>.{18})"
|
|
||||||
"|"
|
|
||||||
"(?<=05)(?P<destinationamount>.{19})\s(?P<currency>.{3})\s"
|
|
||||||
"(?P<exchangerate>.{11})(?P<rateref>.{6})"
|
|
||||||
"|"
|
|
||||||
"(?<=06)(?P<principalinfo1>.{35})(?P<principalinfo2>.{35})"
|
|
||||||
"|"
|
|
||||||
"(?<=07)(?P<bankinfo1>.{35})"
|
|
||||||
"(?P<bankinfo2>.{35})?"
|
|
||||||
"(?P<bankinfo3>.{35})?"
|
|
||||||
"(?P<bankinfo4>.{35})?"
|
|
||||||
"(?P<bankinfo5>.{35})?"
|
|
||||||
"(?P<bankinfo6>.{35})?"
|
|
||||||
"(?P<bankinfo7>.{35})?"
|
|
||||||
"(?P<bankinfo8>.{35})?"
|
|
||||||
"(?P<bankinfo9>.{35})?"
|
|
||||||
"(?P<bankinfo10>.{35})?"
|
|
||||||
"(?P<bankinfo11>.{35})?"
|
|
||||||
"(?P<bankinfo12>.{35})?"
|
|
||||||
"|"
|
|
||||||
"(?<=08)(?P<paymentcode>\d{3})\s(?P<paymentdesc>.{31})"
|
|
||||||
"|"
|
|
||||||
"(?<=09)(?P<recipientname2>.{35})"
|
|
||||||
"|"
|
|
||||||
"(?<=11)(?P<reference>.{35})(?P<recipientiban>.{35})"
|
|
||||||
"(?P<recipientbic>.{35})(?P<recipientnameiban>.{70})"
|
|
||||||
"(?P<sendername>.{70})(?P<senderid>.{35})"
|
|
||||||
"(?P<archivalid>.{70})"
|
|
||||||
")"
|
|
||||||
)
|
|
||||||
recparse["40"] = (
|
|
||||||
"T(?P<recordid>40)(?P<record_len>\d{3})"
|
|
||||||
"(?P<recorddate>\d{6})(?P<balance>.{19})"
|
|
||||||
"(?P<availablefunds>.{19})"
|
|
||||||
)
|
|
||||||
recparse["50"] = (
|
|
||||||
"T(?P<recordid>50)(?P<record_len>\d{3})"
|
|
||||||
"(?P<period>\d)(?P<perioddate>\d{6})"
|
|
||||||
"(?P<depositcount>\d{8})(?P<depositsum>.{19})"
|
|
||||||
"(?P<withdrawcount>\d{8})(?P<withdrawsum>.{19})"
|
|
||||||
)
|
|
||||||
recparse["60"] = (
|
|
||||||
"T(?P<recordid>60)(?P<record_len>\d{3})"
|
|
||||||
"(?P<bankid>.{3})(?P<specialid>01)"
|
|
||||||
"(?P<interestperiodstart>\d{6})-"
|
|
||||||
"(?P<interestperiodend>\d{6})"
|
|
||||||
"(?P<avgbalanceinfo>.)(?P<avgbalance>.{19})"
|
|
||||||
"(?P<interestinfo>.)(?P<interestrate>\d{7})"
|
|
||||||
"(?P<limitbalanceinfo>.)(?P<avglimitbalance>.{19})"
|
|
||||||
"(?P<limitinterestinfo>.)(?P<limitinterestrate>\d{7})"
|
|
||||||
"(?P<limitusageinfo>.)(?P<limitusage>\d{7})"
|
|
||||||
"(?P<permanentbalanceinfo>.)(?P<permanentbalance>.{19})"
|
|
||||||
"(?P<refinterestinfo>.)(?P<refinterestname>.{35})"
|
|
||||||
"(?P<refinterestrate>\d{7})"
|
|
||||||
"(?P<refcreditinfo>.)(?P<refcreditname>.{35})"
|
|
||||||
"(?P<refcreditrate>\d{7})"
|
|
||||||
)
|
|
||||||
recparse["70"] = (
|
|
||||||
"T(?P<recordid>70)(?P<record_len>\d{3})"
|
|
||||||
"(?P<bankid>\d{3})"
|
|
||||||
"(?P<infoline1>.{80})"
|
|
||||||
"(?P<infoline2>.{80})?"
|
|
||||||
"(?P<infoline3>.{80})?"
|
|
||||||
"(?P<infoline4>.{80})?"
|
|
||||||
"(?P<infoline5>.{80})?"
|
|
||||||
"(?P<infoline6>.{80})?"
|
|
||||||
)
|
|
||||||
for record in recparse:
|
|
||||||
recparse[record] = re.compile(recparse[record])
|
|
||||||
self.recparse = recparse
|
|
||||||
|
|
||||||
def parse_record(self, line):
|
|
||||||
"""Docstring for parse_perus
|
|
||||||
|
|
||||||
:param line: description
|
|
||||||
|
|
||||||
:returns: description
|
|
||||||
"""
|
|
||||||
line = fixchars(line)
|
|
||||||
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
|
|
||||||
for field in matchdict.keys():
|
|
||||||
if not matchdict[field]:
|
|
||||||
del matchdict[field]
|
|
||||||
|
|
||||||
matchkeys = set(matchdict.keys())
|
|
||||||
needstrip = set([
|
|
||||||
"bankcontact1", "bankcontact2", "bankcontact3",
|
|
||||||
"customerid", "accountowner", "accountname", "refnr", "formnr",
|
|
||||||
"recipientname", "eventdesc", "recipientaccount", "message",
|
|
||||||
"principalinfo1", "bankinfo1", "bankinfo2", "bankinfo3",
|
|
||||||
"bankinfo4", "bankinfo5", "bankinfo6", "bankinfo7", "bankinfo8",
|
|
||||||
"bankinfo9", "bankinfo10", "bankinfo11", "bankinfo12",
|
|
||||||
"principalinfo2", "paymentdesc", "infoline1", "infoline2",
|
|
||||||
"infoline3", "infoline4", "infoline5", "infoline6",
|
|
||||||
"recipientname2", "recipientnameiban", "sendername"])
|
|
||||||
for field in matchkeys & needstrip:
|
|
||||||
matchdict[field] = matchdict[field].strip()
|
|
||||||
# Convert to int
|
|
||||||
needsint = set([
|
|
||||||
"itemcount", "eventid", "record_len",
|
|
||||||
"depositcount", "withdrawcount"])
|
|
||||||
for field in matchkeys & needsint:
|
|
||||||
matchdict[field] = float(matchdict[field])
|
|
||||||
# Convert to float
|
|
||||||
needsfloat = set([
|
|
||||||
"startingbalance", "accountlimit", "amount",
|
|
||||||
"destinationamount", "balance", "availablefunds", "depositsum",
|
|
||||||
"withdrawsum", "avgbalance", "avglimitbalance",
|
|
||||||
"permanentbalance"])
|
|
||||||
for field in matchkeys & needsfloat:
|
|
||||||
matchdict[field] = float(matchdict[field])
|
|
||||||
# convert sents to euros
|
|
||||||
needseur = set([
|
|
||||||
"startingbalance", "accountlimit", "amount",
|
|
||||||
"destinationamount", "balance", "availablefunds", "depositsum",
|
|
||||||
"withdrawsum", "avgbalance", "permanentbalance"])
|
|
||||||
for field in matchkeys & needseur:
|
|
||||||
matchdict[field] = matchdict[field] / 100
|
|
||||||
# convert ibanswift to separate fields
|
|
||||||
if "ibanswift" in matchdict:
|
|
||||||
matchdict["iban"], matchdict["swift"] = (
|
|
||||||
matchdict["ibanswift"].strip().split()
|
|
||||||
)
|
|
||||||
|
|
||||||
# Convert date fields
|
|
||||||
needdate = set([
|
|
||||||
"startdate", "enddate", "creationdate", "balancedate",
|
|
||||||
"valuedate", "paymentdate", "recorddate", "perioddate"])
|
|
||||||
for field in matchkeys & needdate:
|
|
||||||
# Base all dates on the year 2000, since it's unlikely that this
|
|
||||||
# starndard will survive to see 2020 due to SEPA
|
|
||||||
datestring = matchdict[field]
|
|
||||||
if datestring == '000000':
|
|
||||||
matchdict[field] = None
|
|
||||||
continue
|
|
||||||
|
|
||||||
matchdict[field] = datetime.date(
|
|
||||||
int("20" + datestring[0:2]),
|
|
||||||
int(datestring[2:4]), int(datestring[4:6]))
|
|
||||||
# convert time fields
|
|
||||||
needtime = set(["creationtime"])
|
|
||||||
for field in matchkeys & needtime:
|
|
||||||
timestring = matchdict[field]
|
|
||||||
matchdict[field] = datetime.time(
|
|
||||||
int(timestring[0:2]),
|
|
||||||
int(timestring[2:4]))
|
|
||||||
|
|
||||||
return matchdict
|
|
||||||
|
|
||||||
|
|
||||||
def parse_file(filename):
|
|
||||||
"""Parse file with PATU format inside
|
|
||||||
|
|
||||||
:param filename: description
|
|
||||||
|
|
||||||
:returns: description
|
|
||||||
"""
|
|
||||||
patufile = open(filename, "r")
|
|
||||||
parser = PatuParser()
|
|
||||||
for line in patufile:
|
|
||||||
parser.parse_record(line)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""The main function, currently just calls a dummy filename
|
|
||||||
|
|
||||||
:returns: description
|
|
||||||
"""
|
|
||||||
parse_file("myinput.nda")
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Copyright (C) 2010 Sami Haahtinen (<http://ressukka.net>).
|
|
||||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
"""
|
|
||||||
This parser implements the PATU format support. PATU format is a generic format
|
|
||||||
used by finnish banks.
|
|
||||||
"""
|
|
||||||
from openerp.addons.account_banking.parsers import models
|
|
||||||
from openerp.tools.translate import _
|
|
||||||
from openerp.addons.account_banking_fi_patu.parser import PatuParser
|
|
||||||
|
|
||||||
__all__ = ['Parser']
|
|
||||||
|
|
||||||
|
|
||||||
class Transaction(models.mem_bank_transaction):
|
|
||||||
"""Implementation of transaction communication class for account_banking.
|
|
||||||
"""
|
|
||||||
mapping = {
|
|
||||||
"remote_account": "recipientaccount",
|
|
||||||
"remote_currency": "currency",
|
|
||||||
"transferred_amount": "amount",
|
|
||||||
"execution_date": "recorddate",
|
|
||||||
"value_date": "paymentdate",
|
|
||||||
"transfer_type": "eventtype",
|
|
||||||
"reference": "refnr",
|
|
||||||
"eventcode": "eventcode",
|
|
||||||
"message": "message"
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, record, *args, **kwargs):
|
|
||||||
"""Initialize own dict with read values."""
|
|
||||||
super(Transaction, self).__init__(*args, **kwargs)
|
|
||||||
for key in self.mapping:
|
|
||||||
try:
|
|
||||||
setattr(self, key, record[self.mapping[key]])
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
"""Override validity checks.
|
|
||||||
There are certain situations for PATU which can be validated as
|
|
||||||
invalid, but are normal.
|
|
||||||
If eventcode is 730, the transaction was initiated by the bank and
|
|
||||||
doesn't have a destination account.
|
|
||||||
"""
|
|
||||||
if self.eventcode in ["720", "710"]:
|
|
||||||
# Withdrawal from and deposit to the account
|
|
||||||
return (self.execution_date and self.transferred_amount and True) \
|
|
||||||
or False
|
|
||||||
if self.eventcode and self.eventcode == "730":
|
|
||||||
# The transaction is bank initiated, no remote account is present
|
|
||||||
return (self.execution_date and self.transferred_amount and True) \
|
|
||||||
or False
|
|
||||||
return super(Transaction, self).is_valid()
|
|
||||||
|
|
||||||
|
|
||||||
class statement(models.mem_bank_statement):
|
|
||||||
"""Implementation of bank_statement communication class of account_banking
|
|
||||||
"""
|
|
||||||
def __init__(self, record, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Set decent start values based on first transaction read
|
|
||||||
"""
|
|
||||||
super(statement, self).__init__(*args, **kwargs)
|
|
||||||
self.id = record["statementnr"]
|
|
||||||
self.local_account = self.convert_bank_account(record["accountnr"])
|
|
||||||
self.date = record["creationdate"]
|
|
||||||
self.start_balance = record["startingbalance"]
|
|
||||||
|
|
||||||
def convert_bank_account(self, accountnr):
|
|
||||||
"""Convert bank account number in to a abbreviated format used in
|
|
||||||
finland"""
|
|
||||||
bank = accountnr[:6]
|
|
||||||
account = accountnr[6:].lstrip("0")
|
|
||||||
return "%s-%s" % (bank, account)
|
|
||||||
|
|
||||||
def import_transaction(self, record):
|
|
||||||
"""Import a transaction to the statement"""
|
|
||||||
if record["recordid"] == "40":
|
|
||||||
self.end_balance = record["balance"]
|
|
||||||
elif record["recordid"] == "10" or record["recordid"] == "80":
|
|
||||||
# XXX: Sum up entries that have detailed records set for them. For
|
|
||||||
# now, ignore the parent entry
|
|
||||||
if record["receiptcode"] == "E":
|
|
||||||
return
|
|
||||||
self.transactions.append(Transaction(record))
|
|
||||||
|
|
||||||
|
|
||||||
class Parser(models.parser):
|
|
||||||
code = 'FIPATU'
|
|
||||||
name = _('PATU statement sheet')
|
|
||||||
doc = _('''\
|
|
||||||
PATU statement format defines one or more statements in each file. This parser
|
|
||||||
will parse all statements in a file and import them to OpenERP
|
|
||||||
''')
|
|
||||||
|
|
||||||
def parse(self, cr, data):
|
|
||||||
result = []
|
|
||||||
stmnt = None
|
|
||||||
patuparser = PatuParser()
|
|
||||||
for line in data.splitlines():
|
|
||||||
# Skip empty (last) lines
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
record = patuparser.parse_record(line)
|
|
||||||
if record["recordid"] == "00":
|
|
||||||
# New statement
|
|
||||||
stmnt = statement(record)
|
|
||||||
result.append(stmnt)
|
|
||||||
else:
|
|
||||||
stmnt.import_transaction(record)
|
|
||||||
result.append(stmnt)
|
|
||||||
return result
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# OpenERP, Open Source Management Solution
|
|
||||||
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
|
|
||||||
#
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
from . import mt940
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# OpenERP, Open Source Management Solution
|
|
||||||
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
|
|
||||||
#
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
{
|
|
||||||
"name": "MT940",
|
|
||||||
"version": "1.0",
|
|
||||||
"author": "Therp BV",
|
|
||||||
"complexity": "expert",
|
|
||||||
"description": """
|
|
||||||
This addon provides a generic parser for MT940 files. Given that MT940 is a
|
|
||||||
non-open non-standard of pure evil in the way that every bank cooks up its own
|
|
||||||
interpretation of it, this addon alone won't help you much. It is rather
|
|
||||||
intended to be used by other addons to implement the dialect specific to a
|
|
||||||
certain bank.
|
|
||||||
|
|
||||||
See account_banking_nl_ing_mt940 for an example on how to use it.
|
|
||||||
""",
|
|
||||||
"category": "Dependency",
|
|
||||||
"depends": [
|
|
||||||
'account_banking',
|
|
||||||
],
|
|
||||||
"data": [
|
|
||||||
],
|
|
||||||
"js": [
|
|
||||||
],
|
|
||||||
"css": [
|
|
||||||
],
|
|
||||||
"qweb": [
|
|
||||||
],
|
|
||||||
"auto_install": False,
|
|
||||||
'installable': False,
|
|
||||||
"application": False,
|
|
||||||
"external_dependencies": {
|
|
||||||
'python': [],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
#!/usr/bin/env python2
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# OpenERP, Open Source Management Solution
|
|
||||||
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
|
|
||||||
#
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
"""
|
|
||||||
Parser for MT940 format files
|
|
||||||
"""
|
|
||||||
import re
|
|
||||||
import datetime
|
|
||||||
import logging
|
|
||||||
try:
|
|
||||||
from openerp.addons.account_banking.parsers.models import (
|
|
||||||
mem_bank_statement,
|
|
||||||
mem_bank_transaction,
|
|
||||||
)
|
|
||||||
from openerp.tools.misc import DEFAULT_SERVER_DATE_FORMAT
|
|
||||||
except ImportError:
|
|
||||||
# this allows us to run this file standalone, see __main__ at the end
|
|
||||||
|
|
||||||
class mem_bank_statement:
|
|
||||||
def __init__(self):
|
|
||||||
self.transactions = []
|
|
||||||
|
|
||||||
class mem_bank_transaction:
|
|
||||||
pass
|
|
||||||
DEFAULT_SERVER_DATE_FORMAT = "%Y-%m-%d"
|
|
||||||
|
|
||||||
|
|
||||||
class MT940(object):
|
|
||||||
'''Inherit this class in your account_banking.parsers.models.parser,
|
|
||||||
define functions to handle the tags you need to handle and adjust static
|
|
||||||
variables as needed.
|
|
||||||
|
|
||||||
Note that order matters: You need to do your_parser(MT940, parser), not the
|
|
||||||
other way around!
|
|
||||||
|
|
||||||
At least, you should override handle_tag_61 and handle_tag_86. Don't forget
|
|
||||||
to call super.
|
|
||||||
handle_tag_* functions receive the remainder of the the line (that is,
|
|
||||||
without ':XX:') and are supposed to write into self.current_transaction'''
|
|
||||||
|
|
||||||
header_lines = 3
|
|
||||||
'''One file can contain multiple statements, each with its own poorly
|
|
||||||
documented header. For now, the best thing to do seems to skip that'''
|
|
||||||
|
|
||||||
footer_regex = '^-}$'
|
|
||||||
footer_regex = '^-XXX$'
|
|
||||||
'The line that denotes end of message, we need to create a new statement'
|
|
||||||
|
|
||||||
tag_regex = '^:[0-9]{2}[A-Z]*:'
|
|
||||||
'The beginning of a record, should be anchored to beginning of the line'
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(MT940, self).__init__(*args, **kwargs)
|
|
||||||
'state variables'
|
|
||||||
self.current_statement = None
|
|
||||||
'type account_banking.parsers.models.mem_bank_statement'
|
|
||||||
self.current_transaction = None
|
|
||||||
'type account_banking.parsers.models.mem_bank_transaction'
|
|
||||||
self.statements = []
|
|
||||||
'parsed statements up to now'
|
|
||||||
|
|
||||||
def parse(self, cr, data):
|
|
||||||
'implements account_banking.parsers.models.parser.parse()'
|
|
||||||
iterator = data.replace('\r\n', '\n').split('\n').__iter__()
|
|
||||||
line = None
|
|
||||||
record_line = ''
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
if not self.current_statement:
|
|
||||||
self.handle_header(cr, line, iterator)
|
|
||||||
line = iterator.next()
|
|
||||||
if not self.is_tag(cr, line) and not self.is_footer(cr, line):
|
|
||||||
record_line = self.append_continuation_line(
|
|
||||||
cr, record_line, line)
|
|
||||||
continue
|
|
||||||
if record_line:
|
|
||||||
self.handle_record(cr, record_line)
|
|
||||||
if self.is_footer(cr, line):
|
|
||||||
self.handle_footer(cr, line, iterator)
|
|
||||||
record_line = ''
|
|
||||||
continue
|
|
||||||
record_line = line
|
|
||||||
except StopIteration:
|
|
||||||
pass
|
|
||||||
return self.statements
|
|
||||||
|
|
||||||
def append_continuation_line(self, cr, line, continuation_line):
|
|
||||||
'''append a continuation line for a multiline record.
|
|
||||||
Override and do data cleanups as necessary.'''
|
|
||||||
return line + continuation_line
|
|
||||||
|
|
||||||
def create_statement(self, cr):
|
|
||||||
'''create a mem_bank_statement - override if you need a custom
|
|
||||||
implementation'''
|
|
||||||
return mem_bank_statement()
|
|
||||||
|
|
||||||
def create_transaction(self, cr):
|
|
||||||
'''create a mem_bank_transaction - override if you need a custom
|
|
||||||
implementation'''
|
|
||||||
return mem_bank_transaction()
|
|
||||||
|
|
||||||
def is_footer(self, cr, line):
|
|
||||||
'''determine if a line is the footer of a statement'''
|
|
||||||
return line and bool(re.match(self.footer_regex, line))
|
|
||||||
|
|
||||||
def is_tag(self, cr, line):
|
|
||||||
'''determine if a line has a tag'''
|
|
||||||
return line and bool(re.match(self.tag_regex, line))
|
|
||||||
|
|
||||||
def handle_header(self, cr, line, iterator):
|
|
||||||
'''skip header lines, create current statement'''
|
|
||||||
for i in range(self.header_lines):
|
|
||||||
iterator.next()
|
|
||||||
self.current_statement = self.create_statement(cr)
|
|
||||||
|
|
||||||
def handle_footer(self, cr, line, iterator):
|
|
||||||
'''add current statement to list, reset state'''
|
|
||||||
self.statements.append(self.current_statement)
|
|
||||||
self.current_statement = None
|
|
||||||
|
|
||||||
def handle_record(self, cr, line):
|
|
||||||
'''find a function to handle the record represented by line'''
|
|
||||||
tag_match = re.match(self.tag_regex, line)
|
|
||||||
tag = tag_match.group(0).strip(':')
|
|
||||||
if not hasattr(self, 'handle_tag_%s' % tag):
|
|
||||||
logging.error('Unknown tag %s', tag)
|
|
||||||
logging.error(line)
|
|
||||||
return
|
|
||||||
handler = getattr(self, 'handle_tag_%s' % tag)
|
|
||||||
handler(cr, line[tag_match.end():])
|
|
||||||
|
|
||||||
def handle_tag_20(self, cr, data):
|
|
||||||
'''ignore reference number'''
|
|
||||||
pass
|
|
||||||
|
|
||||||
def handle_tag_25(self, cr, data):
|
|
||||||
'''get account owner information'''
|
|
||||||
self.current_statement.local_account = data
|
|
||||||
|
|
||||||
def handle_tag_28C(self, cr, data):
|
|
||||||
'''get sequence number _within_this_batch_ - this alone
|
|
||||||
doesn't provide a unique id!'''
|
|
||||||
self.current_statement.id = data
|
|
||||||
|
|
||||||
def handle_tag_60F(self, cr, data):
|
|
||||||
'''get start balance and currency'''
|
|
||||||
self.current_statement.local_currency = data[7:10]
|
|
||||||
self.current_statement.date = str2date(data[1:7])
|
|
||||||
self.current_statement.start_balance = \
|
|
||||||
(1 if data[0] == 'C' else -1) * str2float(data[10:])
|
|
||||||
self.current_statement.id = '%s/%s' % (
|
|
||||||
self.current_statement.date.strftime('%Y'),
|
|
||||||
self.current_statement.id)
|
|
||||||
|
|
||||||
def handle_tag_62F(self, cr, data):
|
|
||||||
'''get ending balance'''
|
|
||||||
self.current_statement.end_balance = \
|
|
||||||
(1 if data[0] == 'C' else -1) * str2float(data[10:])
|
|
||||||
|
|
||||||
def handle_tag_64(self, cr, data):
|
|
||||||
'''get current balance in currency'''
|
|
||||||
pass
|
|
||||||
|
|
||||||
def handle_tag_65(self, cr, data):
|
|
||||||
'''get future balance in currency'''
|
|
||||||
pass
|
|
||||||
|
|
||||||
def handle_tag_61(self, cr, data):
|
|
||||||
'''get transaction values'''
|
|
||||||
transaction = self.create_transaction(cr)
|
|
||||||
self.current_statement.transactions.append(transaction)
|
|
||||||
self.current_transaction = transaction
|
|
||||||
transaction.execution_date = str2date(data[:6])
|
|
||||||
transaction.effective_date = str2date(data[:6])
|
|
||||||
transaction.value_date = str2date(data[:6])
|
|
||||||
'...and the rest already is highly bank dependent'
|
|
||||||
|
|
||||||
def handle_tag_86(self, cr, data):
|
|
||||||
'''details for previous transaction, here most differences between
|
|
||||||
banks occur'''
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def str2date(string, fmt='%y%m%d'):
|
|
||||||
return datetime.datetime.strptime(string, fmt)
|
|
||||||
|
|
||||||
|
|
||||||
def str2float(string):
|
|
||||||
return float(string.replace(',', '.'))
|
|
||||||
|
|
||||||
|
|
||||||
def main(filename):
|
|
||||||
"""testing"""
|
|
||||||
parser = MT940()
|
|
||||||
parser.parse(None, open(filename, 'r').read())
|
|
||||||
for statement in parser.statements:
|
|
||||||
print '''statement found for %(local_account)s at %(date)s
|
|
||||||
with %(local_currency)s%(start_balance)s to %(end_balance)s
|
|
||||||
''' % statement.__dict__
|
|
||||||
for transaction in statement.transactions:
|
|
||||||
print '''
|
|
||||||
transaction on %(execution_date)s''' % transaction.__dict__
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
import sys
|
|
||||||
main(*sys.argv[1:])
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import abnamro
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Copyright (C) 2009 - 2011 EduSense BV (<http://www.edusense.nl>)
|
|
||||||
# and Therp BV (<http://therp.nl>)
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
{
|
|
||||||
'name': 'abnamro (NL) Bank Statements Import',
|
|
||||||
'version': '0.1',
|
|
||||||
'license': 'AGPL-3',
|
|
||||||
'author': ['Therp BV', 'EduSense BV'],
|
|
||||||
'website': 'https://launchpad.net/banking-addons',
|
|
||||||
'category': 'Banking addons',
|
|
||||||
'depends': ['account_banking'],
|
|
||||||
'description': '''
|
|
||||||
Import filter for abnamro (NL) bank transaction files (txt/tab format).
|
|
||||||
|
|
||||||
No formal specifications of the file layout are released by abnamro. You can
|
|
||||||
help improve the performance of this import filter on
|
|
||||||
https://launchpad.net/account-banking.
|
|
||||||
|
|
||||||
Imported bank transfers are organized in statements covering periods of one
|
|
||||||
week, even if the imported files cover a different period.
|
|
||||||
''',
|
|
||||||
'installable': False,
|
|
||||||
}
|
|
||||||
@@ -1,394 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>)
|
|
||||||
# 2011 - 2013 Therp BV (<http://therp.nl>)
|
|
||||||
#
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
'''
|
|
||||||
This parser follows the Dutch Banking Tools specifications which are
|
|
||||||
empirically recreated in this module.
|
|
||||||
|
|
||||||
Dutch Banking Tools uses the concept of 'Afschrift' or Bank Statement.
|
|
||||||
Every transaction is bound to a Bank Statement. As such, this module generates
|
|
||||||
Bank Statements along with Bank Transactions.
|
|
||||||
'''
|
|
||||||
from openerp.addons.account_banking.parsers import models
|
|
||||||
from openerp.addons.account_banking.parsers.convert import str2date
|
|
||||||
from openerp.tools.translate import _
|
|
||||||
from openerp.osv import orm
|
|
||||||
|
|
||||||
import re
|
|
||||||
import csv
|
|
||||||
|
|
||||||
__all__ = ['parser']
|
|
||||||
|
|
||||||
bt = models.mem_bank_transaction
|
|
||||||
|
|
||||||
|
|
||||||
class transaction_message(object):
|
|
||||||
'''
|
|
||||||
A auxiliary class to validate and coerce read values
|
|
||||||
'''
|
|
||||||
attrnames = [
|
|
||||||
'local_account', 'local_currency', 'date', 'u1', 'u2', 'date2',
|
|
||||||
'transferred_amount', 'blob',
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, values, subno):
|
|
||||||
'''
|
|
||||||
Initialize own dict with attributes and coerce values to right type
|
|
||||||
'''
|
|
||||||
if len(self.attrnames) != len(values):
|
|
||||||
raise ValueError(
|
|
||||||
_('Invalid transaction line: expected %d columns, found '
|
|
||||||
'%d') % (len(self.attrnames), len(values))
|
|
||||||
)
|
|
||||||
''' Strip all values except the blob '''
|
|
||||||
for (key, val) in zip(self.attrnames, values):
|
|
||||||
self.__dict__[key] = key == 'blob' and val or val.strip()
|
|
||||||
# for lack of a standardized locale function to parse amounts
|
|
||||||
self.local_account = self.local_account.zfill(10)
|
|
||||||
self.transferred_amount = float(
|
|
||||||
self.transferred_amount.replace(',', '.'))
|
|
||||||
self.execution_date = str2date(self.date, '%Y%m%d')
|
|
||||||
self.value_date = str2date(self.date, '%Y%m%d')
|
|
||||||
# Set statement_id based on week number
|
|
||||||
self.statement_id = self.execution_date.strftime('%Yw%W')
|
|
||||||
self.id = str(subno).zfill(4)
|
|
||||||
|
|
||||||
|
|
||||||
class transaction(models.mem_bank_transaction):
|
|
||||||
'''
|
|
||||||
Implementation of transaction communication class for account_banking.
|
|
||||||
'''
|
|
||||||
attrnames = ['local_account', 'local_currency', 'transferred_amount',
|
|
||||||
'blob', 'execution_date', 'value_date', 'id',
|
|
||||||
]
|
|
||||||
|
|
||||||
type_map = {
|
|
||||||
# retrieved from online help in the Triodos banking application
|
|
||||||
'BEA': bt.PAYMENT_TERMINAL, # Pin
|
|
||||||
'GEA': bt.BANK_TERMINAL, # ATM
|
|
||||||
'COSTS': bt.BANK_COSTS,
|
|
||||||
'BANK': bt.ORDER,
|
|
||||||
'GIRO': bt.ORDER,
|
|
||||||
'INTL': bt.ORDER, # international order
|
|
||||||
'UNKN': bt.ORDER, # everything else
|
|
||||||
'SEPA': bt.ORDER,
|
|
||||||
'PAYB': bt.PAYMENT_BATCH,
|
|
||||||
'RETR': bt.STORNO,
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, line, *args, **kwargs):
|
|
||||||
'''
|
|
||||||
Initialize own dict with read values.
|
|
||||||
'''
|
|
||||||
super(transaction, self).__init__(*args, **kwargs)
|
|
||||||
# Copy attributes from auxiliary class to self.
|
|
||||||
for attr in self.attrnames:
|
|
||||||
setattr(self, attr, getattr(line, attr))
|
|
||||||
# Initialize other attributes
|
|
||||||
self.transfer_type = 'UNKN'
|
|
||||||
self.remote_account = ''
|
|
||||||
self.remote_owner = ''
|
|
||||||
self.reference = ''
|
|
||||||
self.message = ''
|
|
||||||
# Decompose structured messages
|
|
||||||
self.parse_message()
|
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
if not self.error_message:
|
|
||||||
if not self.transferred_amount:
|
|
||||||
self.error_message = "No transferred amount"
|
|
||||||
elif not self.execution_date:
|
|
||||||
self.error_message = "No execution date"
|
|
||||||
elif not self.remote_account and self.transfer_type not in [
|
|
||||||
'BEA', 'GEA', 'COSTS', 'UNKN', 'PAYB', ]:
|
|
||||||
self.error_message = _(
|
|
||||||
'No remote account for transaction type %s'
|
|
||||||
) % self.transfer_type
|
|
||||||
if self.error_message:
|
|
||||||
raise orm.except_orm(_('Error !'), _(self.error_message))
|
|
||||||
return not self.error_message
|
|
||||||
|
|
||||||
def parse_message(self):
|
|
||||||
'''
|
|
||||||
Parse structured message parts into appropriate attributes
|
|
||||||
'''
|
|
||||||
def split_blob(line):
|
|
||||||
# here we split up the blob, which the last field in a tab
|
|
||||||
# separated statement line the blob is a *space separated* fixed
|
|
||||||
# field format with field length 32. Empty fields are ignored
|
|
||||||
col = 0
|
|
||||||
size = 33
|
|
||||||
res = []
|
|
||||||
while(len(line) > col * size):
|
|
||||||
separation = (col + 1) * size - 1
|
|
||||||
if line[col * size: separation].strip():
|
|
||||||
part = line[col * size: separation]
|
|
||||||
# If the separation character is not a space, add it anyway
|
|
||||||
# presumably for sepa feedback strings only
|
|
||||||
if (len(line) > separation and line[separation] != ' '):
|
|
||||||
part += line[separation]
|
|
||||||
res.append(part)
|
|
||||||
col += 1
|
|
||||||
return res
|
|
||||||
|
|
||||||
def get_sepa_dict(field):
|
|
||||||
"""
|
|
||||||
Parses a subset of SEPA feedback strings as occur
|
|
||||||
in this non-SEPA csv format.
|
|
||||||
|
|
||||||
The string consists of slash separated KEY/VALUE pairs,
|
|
||||||
but the slash is allowed to and known to occur in VALUE as well!
|
|
||||||
"""
|
|
||||||
def _sepa_message(field, reason):
|
|
||||||
return _(
|
|
||||||
'unable to parse SEPA string: %s - %s' % (field, reason))
|
|
||||||
|
|
||||||
def _get_next_key(items, start):
|
|
||||||
'''Find next key, starting from start, returns the key found,
|
|
||||||
the start position in the array and the end position + 1'''
|
|
||||||
known_keys = [
|
|
||||||
'TRTP', 'IBAN', 'BIC', 'NAME', 'RTRN', 'EREF', 'SWOC',
|
|
||||||
'REMI', 'ADDR', 'CPRP', 'CREF', 'CSID', 'ISDT', 'MARF',
|
|
||||||
'NRTX', 'NRTXR', 'PREF', 'PURP', 'REFOB', 'RREF', 'RTYP',
|
|
||||||
'SVCL', 'SWOD', 'BENM//ID', 'ORDP//ID', 'ORDP//RID',
|
|
||||||
'ORIG//CSID', 'ORIG//MARF', 'ULTD//NAME', 'ULTD//ID',
|
|
||||||
'ULTB//NAME', 'ULTB//ID'
|
|
||||||
]
|
|
||||||
items_len = len(items)
|
|
||||||
start_index = start
|
|
||||||
# Search until start after end of items
|
|
||||||
while start_index < items_len:
|
|
||||||
end_index = start_index + 1
|
|
||||||
while end_index < items_len:
|
|
||||||
key = '/'.join(items[start_index:end_index])
|
|
||||||
if key in known_keys:
|
|
||||||
return (key, start_index, end_index)
|
|
||||||
end_index += 1
|
|
||||||
start_index += 1
|
|
||||||
return False
|
|
||||||
|
|
||||||
items = field[1:].split('/')
|
|
||||||
assert len(items) > 1, _sepa_message(field, _('too few items'))
|
|
||||||
sepa_dict = {}
|
|
||||||
item_index = 0
|
|
||||||
items_len = len(items)
|
|
||||||
key_info = _get_next_key(items, item_index)
|
|
||||||
assert key_info, _sepa_message(
|
|
||||||
field, _('no key found for start %d') % item_index)
|
|
||||||
assert key_info[1] == 0, _sepa_message(
|
|
||||||
field, _('invalid data found before key %s') % key_info[0])
|
|
||||||
while key_info:
|
|
||||||
sepa_key = key_info[0]
|
|
||||||
item_index = key_info[2]
|
|
||||||
# Find where next key - if any - starts
|
|
||||||
key_info = _get_next_key(items, item_index)
|
|
||||||
value_end_index = (key_info and key_info[1]) or items_len
|
|
||||||
sepa_value = (
|
|
||||||
(
|
|
||||||
(value_end_index > item_index)
|
|
||||||
and '/'.join(items[item_index:value_end_index]))
|
|
||||||
or '')
|
|
||||||
sepa_dict[sepa_key] = sepa_value
|
|
||||||
return sepa_dict
|
|
||||||
|
|
||||||
def parse_type(field):
|
|
||||||
# here we process the first field, which identifies the statement
|
|
||||||
# type and in case of certain types contains additional information
|
|
||||||
transfer_type = 'UNKN'
|
|
||||||
remote_account = False
|
|
||||||
remote_owner = False
|
|
||||||
if field.startswith('/TRTP/'):
|
|
||||||
transfer_type = 'SEPA'
|
|
||||||
elif field.startswith('GIRO '):
|
|
||||||
transfer_type = 'GIRO'
|
|
||||||
# field has markup 'GIRO ACCOUNT OWNER'
|
|
||||||
# separated by clusters of space of varying size
|
|
||||||
account_match = re.match('\s*([0-9]+)\s(.*)$', field[5:])
|
|
||||||
if account_match:
|
|
||||||
remote_account = account_match.group(1).zfill(10)
|
|
||||||
remote_owner = account_match.group(2).strip() or ''
|
|
||||||
else:
|
|
||||||
raise orm.except_orm(
|
|
||||||
_('Error !'),
|
|
||||||
_('unable to parse GIRO string: %s') % field)
|
|
||||||
elif field.startswith('BEA '):
|
|
||||||
transfer_type = 'BEA'
|
|
||||||
# columns 6 to 16 contain the terminal identifier
|
|
||||||
# column 17 contains a space
|
|
||||||
# columns 18 to 31 contain date and time in DD.MM.YY/HH.MM
|
|
||||||
# format
|
|
||||||
elif field.startswith('GEA '):
|
|
||||||
transfer_type = 'GEA'
|
|
||||||
# columns 6 to 16 contain the terminal identifier
|
|
||||||
# column 17 contains a space
|
|
||||||
# columns 18 to 31 contain date and time in DD.MM.YY/HH.MM
|
|
||||||
# format
|
|
||||||
elif field.startswith('MAANDBIJDRAGE ABNAMRO'):
|
|
||||||
transfer_type = 'COSTS'
|
|
||||||
elif re.match("^\s([0-9]+\.){3}[0-9]+\s", field):
|
|
||||||
transfer_type = 'BANK'
|
|
||||||
remote_account = field[1:13].strip().replace('.', '').zfill(10)
|
|
||||||
# column 14 to 31 is either empty or contains the remote owner
|
|
||||||
remote_owner = field[14:32].strip()
|
|
||||||
elif re.match("^EL[0-9]{13}I", field):
|
|
||||||
transfer_type = 'INTL'
|
|
||||||
elif field.startswith("TOTAAL BETALINGEN"):
|
|
||||||
transfer_type = 'PAYB'
|
|
||||||
return (transfer_type, remote_account, remote_owner)
|
|
||||||
|
|
||||||
fields = split_blob(self.blob)
|
|
||||||
(self.transfer_type, self.remote_account, self.remote_owner) = \
|
|
||||||
parse_type(fields[0])
|
|
||||||
|
|
||||||
if self.transfer_type == 'SEPA':
|
|
||||||
sepa_dict = get_sepa_dict(''.join(fields))
|
|
||||||
sepa_type = sepa_dict.get('TRTP') or ''
|
|
||||||
self.transfer_type = {
|
|
||||||
'SEPA BATCH': 'PAYB',
|
|
||||||
'SEPA BATCH SALARIS': 'PAYB',
|
|
||||||
'SEPA TERUGBOEKING': 'RETR',
|
|
||||||
}.get(sepa_type.upper(), 'SEPA')
|
|
||||||
self.remote_account = sepa_dict.get('IBAN', False)
|
|
||||||
self.remote_bank_bic = sepa_dict.get('BIC', False)
|
|
||||||
self.remote_owner = sepa_dict.get('NAME', False)
|
|
||||||
self.reference = sepa_dict.get('REMI', '')
|
|
||||||
|
|
||||||
# extract other information depending on type
|
|
||||||
elif self.transfer_type == 'GIRO':
|
|
||||||
if not self.remote_owner and len(fields) > 1:
|
|
||||||
# OWNER is listed in the second field if not in the first
|
|
||||||
self.remote_owner = fields[1].strip() or False
|
|
||||||
fields = [fields[0]] + fields[2:]
|
|
||||||
self.message = ' '.join(field.strip() for field in fields[1:])
|
|
||||||
|
|
||||||
elif self.transfer_type == 'BEA':
|
|
||||||
# second column contains remote owner and bank pass identification
|
|
||||||
self.remote_owner = (
|
|
||||||
len(fields) > 1 and fields[1].split(',')[0].strip() or False)
|
|
||||||
# column 2 and up can contain additional messsages
|
|
||||||
# (such as transaction costs or currency conversion)
|
|
||||||
self.message = ' '.join(field.strip() for field in fields)
|
|
||||||
|
|
||||||
elif self.transfer_type == 'BANK':
|
|
||||||
# second column contains the remote owner or the first message line
|
|
||||||
if not self.remote_owner:
|
|
||||||
self.remote_owner = (
|
|
||||||
len(fields) > 1 and fields[1].strip() or False)
|
|
||||||
self.message = ' '.join(field.strip() for field in fields[2:])
|
|
||||||
else:
|
|
||||||
self.message = ' '.join(field.strip() for field in fields[1:])
|
|
||||||
|
|
||||||
elif self.transfer_type == 'INTL':
|
|
||||||
# first column seems to consist of some kind of international
|
|
||||||
# transaction id
|
|
||||||
self.reference = fields[0].strip()
|
|
||||||
# second column seems to contain remote currency and amount
|
|
||||||
# to be processed in a later release of this module
|
|
||||||
self.message = len(fields) > 1 and fields[1].strip() or False
|
|
||||||
# third column contains iban, preceeded by a slash forward
|
|
||||||
if len(fields) > 2:
|
|
||||||
if fields[2].startswith('/'):
|
|
||||||
self.remote_account = fields[2][1:].strip()
|
|
||||||
else:
|
|
||||||
self.remote_account = fields[2].strip()
|
|
||||||
# fourth column contains remote owner
|
|
||||||
self.remote_owner = (len(fields) > 3 and fields[3].strip() or
|
|
||||||
False)
|
|
||||||
self.message += ' ' + (
|
|
||||||
' '.join(field.strip() for field in fields[4:]))
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.message = ' '.join(field.strip() for field in fields)
|
|
||||||
|
|
||||||
if not self.reference:
|
|
||||||
# the reference is sometimes flagged by the prefix "BETALINGSKENM."
|
|
||||||
# but can be any numeric line really
|
|
||||||
for field in fields[1:]:
|
|
||||||
m = re.match(
|
|
||||||
"^\s*((BETALINGSKENM\.)|(ACCEPTGIRO))?\s*([0-9]+"
|
|
||||||
"([ /][0-9]+)*)\s*$",
|
|
||||||
field)
|
|
||||||
if m:
|
|
||||||
self.reference = m.group(4)
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
class statement(models.mem_bank_statement):
|
|
||||||
'''
|
|
||||||
Implementation of bank_statement communication class of account_banking
|
|
||||||
'''
|
|
||||||
def __init__(self, msg, *args, **kwargs):
|
|
||||||
'''
|
|
||||||
Set decent start values based on first transaction read
|
|
||||||
'''
|
|
||||||
super(statement, self).__init__(*args, **kwargs)
|
|
||||||
self.id = msg.statement_id
|
|
||||||
self.local_account = msg.local_account
|
|
||||||
self.date = str2date(msg.date, '%Y%m%d')
|
|
||||||
self.start_balance = self.end_balance = 0 # msg.start_balance
|
|
||||||
self.import_transaction(msg)
|
|
||||||
|
|
||||||
def import_transaction(self, msg):
|
|
||||||
'''
|
|
||||||
Import a transaction and keep some house holding in the mean time.
|
|
||||||
'''
|
|
||||||
trans = transaction(msg)
|
|
||||||
self.end_balance += trans.transferred_amount
|
|
||||||
self.transactions.append(trans)
|
|
||||||
|
|
||||||
|
|
||||||
class parser(models.parser):
|
|
||||||
code = 'ABNAM'
|
|
||||||
country_code = 'NL'
|
|
||||||
name = _('Abnamro (NL)')
|
|
||||||
doc = _('''\
|
|
||||||
The Dutch Abnamro format is a tab separated text format. The last of these
|
|
||||||
fields is itself a fixed length array containing transaction type, remote
|
|
||||||
account and owner. The bank does not provide a formal specification of the
|
|
||||||
format. Transactions are not explicitely tied to bank statements, although
|
|
||||||
each file covers a period of two weeks.
|
|
||||||
''')
|
|
||||||
|
|
||||||
def parse(self, cr, data):
|
|
||||||
result = []
|
|
||||||
stmnt = None
|
|
||||||
lines = data.split('\n')
|
|
||||||
# Transaction lines are not numbered, so keep a tracer
|
|
||||||
subno = 0
|
|
||||||
statement_id = False
|
|
||||||
for line in csv.reader(lines, delimiter='\t', quoting=csv.QUOTE_NONE):
|
|
||||||
# Skip empty (last) lines
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
subno += 1
|
|
||||||
msg = transaction_message(line, subno)
|
|
||||||
if not statement_id:
|
|
||||||
statement_id = self.get_unique_statement_id(
|
|
||||||
cr, msg.execution_date.strftime('%Yw%W'))
|
|
||||||
msg.statement_id = statement_id
|
|
||||||
if stmnt:
|
|
||||||
stmnt.import_transaction(msg)
|
|
||||||
else:
|
|
||||||
stmnt = statement(msg)
|
|
||||||
result.append(stmnt)
|
|
||||||
return result
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
# Translation of OpenERP Server.
|
|
||||||
# This file contains the translation of the following modules:
|
|
||||||
# * account_banking_nl_abnamro
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: OpenERP Server 7.0\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2013-10-25 15:53+0000\n"
|
|
||||||
"PO-Revision-Date: 2013-10-25 15:53+0000\n"
|
|
||||||
"Last-Translator: <>\n"
|
|
||||||
"Language-Team: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: \n"
|
|
||||||
"Plural-Forms: \n"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:122
|
|
||||||
#, python-format
|
|
||||||
msgid "No remote account for transaction type %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:348
|
|
||||||
#, python-format
|
|
||||||
msgid "Abnamro (NL)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:197
|
|
||||||
#, python-format
|
|
||||||
msgid "invalid data found before key %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:125
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:229
|
|
||||||
#, python-format
|
|
||||||
msgid "Error !"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:261
|
|
||||||
#, python-format
|
|
||||||
msgid "Sepa transaction type %s not handled yet"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:60
|
|
||||||
#, python-format
|
|
||||||
msgid "Invalid transaction line: expected %d columns, found %d"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:189
|
|
||||||
#, python-format
|
|
||||||
msgid "too few items"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:195
|
|
||||||
#, python-format
|
|
||||||
msgid "no key found for start %d"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:162
|
|
||||||
#, python-format
|
|
||||||
msgid "unable to parse SEPA string: %s - %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:230
|
|
||||||
#, python-format
|
|
||||||
msgid "unable to parse GIRO string: %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:349
|
|
||||||
#, python-format
|
|
||||||
msgid "The Dutch Abnamro format is a tab separated text format. The last of these\n"
|
|
||||||
"fields is itself a fixed length array containing transaction type, remote\n"
|
|
||||||
"account and owner. The bank does not provide a formal specification of the\n"
|
|
||||||
"format. Transactions are not explicitely tied to bank statements, although\n"
|
|
||||||
"each file covers a period of two weeks.\n"
|
|
||||||
""
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
# Translation of OpenERP Server.
|
|
||||||
# This file contains the translation of the following modules:
|
|
||||||
# * account_banking_nl_abnamro
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: OpenERP Server 6.0.1\n"
|
|
||||||
"Report-Msgid-Bugs-To: support@openerp.com\n"
|
|
||||||
"POT-Creation-Date: 2013-10-25 15:53+0000\n"
|
|
||||||
"PO-Revision-Date: 2013-11-11 17:48+0000\n"
|
|
||||||
"Last-Translator: Pedro Manuel Baeza <pedro.baeza@gmail.com>\n"
|
|
||||||
"Language-Team: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"X-Launchpad-Export-Date: 2014-05-31 06:02+0000\n"
|
|
||||||
"X-Generator: Launchpad (build 17031)\n"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:122
|
|
||||||
#, python-format
|
|
||||||
msgid "No remote account for transaction type %s"
|
|
||||||
msgstr "No remote account for transaction type %s"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:348
|
|
||||||
#, python-format
|
|
||||||
msgid "Abnamro (NL)"
|
|
||||||
msgstr "Abnamro (NL)"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:197
|
|
||||||
#, python-format
|
|
||||||
msgid "invalid data found before key %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:125
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:229
|
|
||||||
#, python-format
|
|
||||||
msgid "Error !"
|
|
||||||
msgstr "Error !"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:261
|
|
||||||
#, python-format
|
|
||||||
msgid "Sepa transaction type %s not handled yet"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:60
|
|
||||||
#, python-format
|
|
||||||
msgid "Invalid transaction line: expected %d columns, found %d"
|
|
||||||
msgstr "Invalid transaction line: expected %d columns, found %d"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:189
|
|
||||||
#, python-format
|
|
||||||
msgid "too few items"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:195
|
|
||||||
#, python-format
|
|
||||||
msgid "no key found for start %d"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:162
|
|
||||||
#, python-format
|
|
||||||
msgid "unable to parse SEPA string: %s - %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:230
|
|
||||||
#, python-format
|
|
||||||
msgid "unable to parse GIRO string: %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:349
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"The Dutch Abnamro format is a tab separated text format. The last of these\n"
|
|
||||||
"fields is itself a fixed length array containing transaction type, remote\n"
|
|
||||||
"account and owner. The bank does not provide a formal specification of the\n"
|
|
||||||
"format. Transactions are not explicitely tied to bank statements, although\n"
|
|
||||||
"each file covers a period of two weeks.\n"
|
|
||||||
msgstr ""
|
|
||||||
"The Dutch Abnamro format is a tab separated text format. The last of these\n"
|
|
||||||
"fields is itself a fixed length array containing transaction type, remote\n"
|
|
||||||
"account and owner. The bank does not provide a formal specification of the\n"
|
|
||||||
"format. Transactions are not explicitely tied to bank statements, although\n"
|
|
||||||
"each file covers a period of two weeks.\n"
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
# Translation of OpenERP Server.
|
|
||||||
# This file contains the translation of the following modules:
|
|
||||||
# * account_banking_nl_abnamro
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: OpenERP Server 6.0.1\n"
|
|
||||||
"Report-Msgid-Bugs-To: support@openerp.com\n"
|
|
||||||
"POT-Creation-Date: 2013-10-25 15:53+0000\n"
|
|
||||||
"PO-Revision-Date: 2013-12-03 11:09+0000\n"
|
|
||||||
"Last-Translator: Erwin van der Ploeg (BAS Solutions) <Unknown>\n"
|
|
||||||
"Language-Team: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"X-Launchpad-Export-Date: 2014-05-31 06:02+0000\n"
|
|
||||||
"X-Generator: Launchpad (build 17031)\n"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:122
|
|
||||||
#, python-format
|
|
||||||
msgid "No remote account for transaction type %s"
|
|
||||||
msgstr "Geen tegenrekening bij transactietype %s"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:348
|
|
||||||
#, python-format
|
|
||||||
msgid "Abnamro (NL)"
|
|
||||||
msgstr "Abnamro (NL)"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:197
|
|
||||||
#, python-format
|
|
||||||
msgid "invalid data found before key %s"
|
|
||||||
msgstr "Ongeldige gegevesn gevonden voor key %s"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:125
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:229
|
|
||||||
#, python-format
|
|
||||||
msgid "Error !"
|
|
||||||
msgstr "Fout !"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:261
|
|
||||||
#, python-format
|
|
||||||
msgid "Sepa transaction type %s not handled yet"
|
|
||||||
msgstr "Sepa transactie type %s kan nog niet worden verwerkt"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:60
|
|
||||||
#, python-format
|
|
||||||
msgid "Invalid transaction line: expected %d columns, found %d"
|
|
||||||
msgstr "Ongeldige transactieregel: %d kolommen verwacht, %d aangetroffen"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:189
|
|
||||||
#, python-format
|
|
||||||
msgid "too few items"
|
|
||||||
msgstr "te weinig items"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:195
|
|
||||||
#, python-format
|
|
||||||
msgid "no key found for start %d"
|
|
||||||
msgstr "Geen key gevonden voor start %d"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:162
|
|
||||||
#, python-format
|
|
||||||
msgid "unable to parse SEPA string: %s - %s"
|
|
||||||
msgstr "Niet mogelijk om SEPA string: %s - %s te verwerken"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:230
|
|
||||||
#, python-format
|
|
||||||
msgid "unable to parse GIRO string: %s"
|
|
||||||
msgstr "Niet mogelijk om GIRO string: %s te verwerken"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_abnamro
|
|
||||||
#: code:addons/account_banking_nl_abnamro/abnamro.py:349
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"The Dutch Abnamro format is a tab separated text format. The last of these\n"
|
|
||||||
"fields is itself a fixed length array containing transaction type, remote\n"
|
|
||||||
"account and owner. The bank does not provide a formal specification of the\n"
|
|
||||||
"format. Transactions are not explicitely tied to bank statements, although\n"
|
|
||||||
"each file covers a period of two weeks.\n"
|
|
||||||
msgstr ""
|
|
||||||
"Het Nederlandse ABNAMRO-formaat is een tab-gescheiden tekstformaat. De "
|
|
||||||
"laatste\n"
|
|
||||||
"van deze velden is een tabel van vaste lengte met een transactiesoort, "
|
|
||||||
"tegenrekening\n"
|
|
||||||
"en eigenaar. De bank geeft geen formele specificatie van het formaat. "
|
|
||||||
"Transacties\n"
|
|
||||||
"zijn niet expliciet verbonden met een bankafschrift, alhoewel elk bestand "
|
|
||||||
"een periode\n"
|
|
||||||
"van twee weken beslaat.\n"
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
|
||||||
# All Rights Reserved
|
|
||||||
#
|
|
||||||
# WARNING: This program as such is intended to be used by professional
|
|
||||||
# programmers who take the whole responsability of assessing all potential
|
|
||||||
# consequences resulting from its eventual inadequacies and bugs
|
|
||||||
# End users who are looking for a ready-to-use solution with commercial
|
|
||||||
# garantees and support are strongly adviced to contract EduSense BV
|
|
||||||
#
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
from . import girotel
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
{
|
|
||||||
'name': 'Account Banking - Girotel',
|
|
||||||
'version': '0.62',
|
|
||||||
'license': 'AGPL-3',
|
|
||||||
'author': 'EduSense BV',
|
|
||||||
'website': 'http://www.edusense.nl',
|
|
||||||
'category': 'Account Banking',
|
|
||||||
'depends': ['account_banking'],
|
|
||||||
'data': [
|
|
||||||
],
|
|
||||||
'description': '''
|
|
||||||
Module to import Dutch Girotel format transation files.
|
|
||||||
|
|
||||||
This modules contains no logic, just an import filter for account_banking.
|
|
||||||
''',
|
|
||||||
'installable': False,
|
|
||||||
}
|
|
||||||
@@ -1,374 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
'''
|
|
||||||
This parser follows the Dutch Girotel specifications which are
|
|
||||||
empirically recreated in this module.
|
|
||||||
There is very little information for validating the format or the content
|
|
||||||
within.
|
|
||||||
|
|
||||||
Dutch Girotel uses no concept of 'Afschrift' or Bank Statement.
|
|
||||||
To overcome a lot of problems, this module generates reproducible Bank
|
|
||||||
Staments per months period.
|
|
||||||
|
|
||||||
Transaction ID's are missing, but generated on the fly based on transaction
|
|
||||||
date and sequence position therein.
|
|
||||||
|
|
||||||
Assumptions:
|
|
||||||
1. transactions are sorted in ascending order of date.
|
|
||||||
2. new transactions are appended after previously known transactions of
|
|
||||||
the same date
|
|
||||||
3. banks maintain order in transaction lists within a single date
|
|
||||||
4. the data comes from the SWIFT-network (limited ASCII)
|
|
||||||
|
|
||||||
Assumption 4 seems not always true, leading to wrong character conversions.
|
|
||||||
As a counter measure, all imported data is converted to SWIFT-format before
|
|
||||||
usage.
|
|
||||||
'''
|
|
||||||
from account_banking.parsers import models
|
|
||||||
from account_banking.parsers.convert import str2date, to_swift
|
|
||||||
from tools.translate import _
|
|
||||||
import re
|
|
||||||
import csv
|
|
||||||
|
|
||||||
bt = models.mem_bank_transaction
|
|
||||||
|
|
||||||
__all__ = ['parser']
|
|
||||||
|
|
||||||
|
|
||||||
class transaction_message(object):
|
|
||||||
'''
|
|
||||||
A auxiliary class to validate and coerce read values
|
|
||||||
'''
|
|
||||||
attrnames = [
|
|
||||||
'local_account', 'date', 'transfer_type', 'u1',
|
|
||||||
'remote_account', 'remote_owner', 'u2', 'transferred_amount',
|
|
||||||
'direction', 'u3', 'message', 'remote_currency',
|
|
||||||
]
|
|
||||||
# Attributes with possible non-ASCII string content
|
|
||||||
strattrs = [
|
|
||||||
'remote_owner', 'message'
|
|
||||||
]
|
|
||||||
|
|
||||||
ids = {}
|
|
||||||
|
|
||||||
def __setattribute__(self, attr, value):
|
|
||||||
'''
|
|
||||||
Convert values for string content to SWIFT-allowable content
|
|
||||||
'''
|
|
||||||
if attr != 'strattrs' and attr in self.strattrs:
|
|
||||||
value = to_swift(value)
|
|
||||||
super(transaction_message, self).__setattribute__(attr, value)
|
|
||||||
|
|
||||||
def __getattribute__(self, attr):
|
|
||||||
'''
|
|
||||||
Convert values from string content to SWIFT-allowable content
|
|
||||||
'''
|
|
||||||
retval = super(transaction_message, self).__getattribute__(attr)
|
|
||||||
return attr != (
|
|
||||||
'strattrs'
|
|
||||||
and attr in self.strattrs
|
|
||||||
and to_swift(retval)
|
|
||||||
or retval
|
|
||||||
)
|
|
||||||
|
|
||||||
def genid(self):
|
|
||||||
'''
|
|
||||||
Generate a new id when not assigned before
|
|
||||||
'''
|
|
||||||
if (not hasattr(self, 'id')) or not self.id:
|
|
||||||
if self.date in self.ids:
|
|
||||||
self.ids[self.date] += 1
|
|
||||||
else:
|
|
||||||
self.ids[self.date] = 1
|
|
||||||
self.id = self.date.strftime('%%Y%%m%%d%04d' % self.ids[self.date])
|
|
||||||
|
|
||||||
def __init__(self, values):
|
|
||||||
'''
|
|
||||||
Initialize own dict with attributes and coerce values to right type
|
|
||||||
'''
|
|
||||||
if len(self.attrnames) != len(values):
|
|
||||||
raise ValueError(
|
|
||||||
_('Invalid transaction line: expected %d columns, found %d')
|
|
||||||
% (len(self.attrnames), len(values))
|
|
||||||
)
|
|
||||||
self.__dict__.update(dict(zip(self.attrnames, values)))
|
|
||||||
self.date = str2date(self.date, '%Y%m%d')
|
|
||||||
if self.direction == 'A':
|
|
||||||
self.transferred_amount = -float(self.transferred_amount)
|
|
||||||
# payment batch done via clieop
|
|
||||||
if (self.transfer_type == 'VZ'
|
|
||||||
and (not self.remote_account or self.remote_account == '0')
|
|
||||||
and (not self.message or re.match('^\s*$', self.message))
|
|
||||||
and self.remote_owner.startswith('TOTAAL ')):
|
|
||||||
self.transfer_type = 'PB'
|
|
||||||
self.message = self.remote_owner
|
|
||||||
self.remove_owner = False
|
|
||||||
# payment batch done via sepa
|
|
||||||
if self.transfer_type == 'VZ'\
|
|
||||||
and not self.remote_account\
|
|
||||||
and not self.remote_owner\
|
|
||||||
and re.match(
|
|
||||||
'^Verzamel Eurobetaling .* TOTAAL \d+ POSTEN\s*$',
|
|
||||||
self.message):
|
|
||||||
self.transfer_type = 'PB'
|
|
||||||
else:
|
|
||||||
self.transferred_amount = float(self.transferred_amount)
|
|
||||||
self.local_account = self.local_account.zfill(10)
|
|
||||||
if self.transfer_type != 'DV':
|
|
||||||
self.remote_account = self.remote_account.zfill(10)
|
|
||||||
else:
|
|
||||||
self.remote_account = False
|
|
||||||
self.execution_date = self.value_date = self.date
|
|
||||||
self.remote_owner = self.remote_owner.rstrip()
|
|
||||||
self.message = self.message.rstrip()
|
|
||||||
self.genid()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def statement_id(self):
|
|
||||||
'''Return calculated statement id'''
|
|
||||||
return self.id[:6]
|
|
||||||
|
|
||||||
|
|
||||||
class transaction(models.mem_bank_transaction):
|
|
||||||
'''
|
|
||||||
Implementation of transaction communication class for account_banking.
|
|
||||||
'''
|
|
||||||
attrnames = ['statement_id', 'remote_account', 'remote_owner',
|
|
||||||
'remote_currency', 'transferred_amount', 'execution_date',
|
|
||||||
'value_date', 'transfer_type', 'message',
|
|
||||||
]
|
|
||||||
|
|
||||||
type_map = {
|
|
||||||
'BA': bt.PAYMENT_TERMINAL,
|
|
||||||
'BT': bt.ORDER,
|
|
||||||
'DV': bt.BANK_COSTS,
|
|
||||||
'GM': bt.BANK_TERMINAL,
|
|
||||||
'GT': bt.ORDER,
|
|
||||||
'IC': bt.DIRECT_DEBIT,
|
|
||||||
'OV': bt.ORDER,
|
|
||||||
'VZ': bt.ORDER,
|
|
||||||
'PB': bt.PAYMENT_BATCH,
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, line, *args, **kwargs):
|
|
||||||
'''
|
|
||||||
Initialize own dict with read values.
|
|
||||||
'''
|
|
||||||
super(transaction, self).__init__(*args, **kwargs)
|
|
||||||
for attr in self.attrnames:
|
|
||||||
setattr(self, attr, getattr(line, attr))
|
|
||||||
self.id = line.id.replace(line.statement_id, '')
|
|
||||||
self.reference = self.message[:32].rstrip()
|
|
||||||
self.parse_message()
|
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
'''
|
|
||||||
There are a few situations that can be signaled as 'invalid' but are
|
|
||||||
valid nontheless:
|
|
||||||
1. Invoices from the bank itself are communicated through statements.
|
|
||||||
These too have no remote_account and no remote_owner. They have a
|
|
||||||
transfer_type set to 'DV'.
|
|
||||||
2. Transfers sent through the 'International Transfers' system get
|
|
||||||
their feedback rerouted through a statement, which is not designed to
|
|
||||||
hold the extra fields needed. These transfers have their transfer_type
|
|
||||||
set to 'BT'.
|
|
||||||
3. Cash payments with debit cards are not seen as a transfer between
|
|
||||||
accounts, but as a cash withdrawal. These withdrawals have their
|
|
||||||
transfer_type set to 'BA'.
|
|
||||||
4. Cash withdrawals from banks are too not seen as a transfer between
|
|
||||||
two accounts - the cash exits the banking system. These withdrawals
|
|
||||||
have their transfer_type set to 'GM'.
|
|
||||||
5. Aggregated payment batches. These transactions have transfer type
|
|
||||||
'VZ' natively but are changed to 'PB' while parsing. These transactions
|
|
||||||
have no remote account.
|
|
||||||
'''
|
|
||||||
return bool(self.transferred_amount and self.execution_date and (
|
|
||||||
self.remote_account or
|
|
||||||
self.transfer_type in [
|
|
||||||
'DV', 'PB', 'BT', 'BA', 'GM',
|
|
||||||
]))
|
|
||||||
|
|
||||||
def refold_message(self, message):
|
|
||||||
'''
|
|
||||||
Refold a previously chopped and fixed length message back into one
|
|
||||||
line
|
|
||||||
'''
|
|
||||||
msg, message = message.rstrip(), None
|
|
||||||
parts = [msg[i:i+32].rstrip() for i in range(0, len(msg), 32)]
|
|
||||||
return '\n'.join(parts)
|
|
||||||
|
|
||||||
def parse_message(self):
|
|
||||||
'''
|
|
||||||
Parse the message as sent by the bank. Most messages are composed
|
|
||||||
of chunks of 32 characters, but there are exceptions.
|
|
||||||
'''
|
|
||||||
if self.transfer_type == 'VZ':
|
|
||||||
# Credit bank costs (interest) gets a special treatment.
|
|
||||||
if self.remote_owner.startswith('RC AFREK. REK. '):
|
|
||||||
self.transfer_type = 'DV'
|
|
||||||
|
|
||||||
if self.transfer_type == 'DV':
|
|
||||||
# Bank costs.
|
|
||||||
# Title of action is in remote_owner, message contains additional
|
|
||||||
# info
|
|
||||||
self.reference = self.remote_owner.rstrip()
|
|
||||||
parts = [self.message[i:i+32].rstrip()
|
|
||||||
for i in range(0, len(self.message), 32)
|
|
||||||
]
|
|
||||||
if len(parts) > 3:
|
|
||||||
self.reference = parts[-1]
|
|
||||||
self.message = '\n'.join(parts[:-1])
|
|
||||||
else:
|
|
||||||
self.message = '\n'.join(parts)
|
|
||||||
self.remote_owner = ''
|
|
||||||
|
|
||||||
elif self.transfer_type == 'BA':
|
|
||||||
# Payment through bank terminal
|
|
||||||
# Id of terminal and some owner info is part of message
|
|
||||||
if self.execution_date < str2date('20091130', '%Y%m%d'):
|
|
||||||
parts = self.remote_owner.split('>')
|
|
||||||
else:
|
|
||||||
parts = self.remote_owner.split('>\\')
|
|
||||||
self.remote_owner = ' '.join(parts[0].split()[1:])
|
|
||||||
if len(parts) > 1 and len(parts[1]) > 2:
|
|
||||||
self.remote_owner_city = parts[1]
|
|
||||||
self.message = self.refold_message(self.message)
|
|
||||||
self.reference = '%s %s' % (self.remote_owner,
|
|
||||||
' '.join(self.message.split()[2:4])
|
|
||||||
)
|
|
||||||
|
|
||||||
elif self.transfer_type == 'IC':
|
|
||||||
# Direct debit - remote_owner containts reference, while
|
|
||||||
# remote_owner is part of the message, most often as
|
|
||||||
# first part of the message.
|
|
||||||
# Sometimes this misfires, as with the tax office collecting road
|
|
||||||
# taxes, but then a once-only manual correction is sufficient.
|
|
||||||
parts = [self.message[i:i+32].rstrip()
|
|
||||||
for i in range(0, len(self.message), 32)
|
|
||||||
]
|
|
||||||
self.reference = self.remote_owner
|
|
||||||
|
|
||||||
if not parts:
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.reference.startswith('KN: '):
|
|
||||||
self.reference = self.reference[4:]
|
|
||||||
if parts[0] == self.reference:
|
|
||||||
parts = parts[1:]
|
|
||||||
# The tax administration office seems to be the notorious exception
|
|
||||||
# to the rule
|
|
||||||
if parts[-1] == 'BELASTINGDIENST':
|
|
||||||
self.remote_owner = parts[-1].capitalize()
|
|
||||||
parts = parts[:-1]
|
|
||||||
else:
|
|
||||||
self.remote_owner = parts[0]
|
|
||||||
parts = parts[1:]
|
|
||||||
# Leave the message, to assist in manual correction of misfires
|
|
||||||
self.message = '\n'.join(parts)
|
|
||||||
|
|
||||||
elif self.transfer_type == 'GM':
|
|
||||||
# Cash withdrawal from a bank terminal
|
|
||||||
# Structured remote_owner message containing bank info and location
|
|
||||||
if self.remote_owner.startswith('OPL. CHIPKNIP'):
|
|
||||||
# Transferring cash to debit card
|
|
||||||
self.remote_account = self.local_account
|
|
||||||
self.message = '%s: %s' % (self.remote_owner, self.message)
|
|
||||||
else:
|
|
||||||
if self.execution_date < str2date('20091130', '%Y%m%d'):
|
|
||||||
parts = self.remote_owner.split('>')
|
|
||||||
else:
|
|
||||||
parts = self.remote_owner.split('>\\')
|
|
||||||
if len(parts) > 1:
|
|
||||||
self.reference = ' '.join([x.rstrip() for x in parts])
|
|
||||||
else:
|
|
||||||
self.reference = 'ING BANK NV %s' % parts[0].split(' ')[0]
|
|
||||||
self.remote_owner = ''
|
|
||||||
|
|
||||||
elif self.transfer_type == 'GT':
|
|
||||||
# Normal transaction, but remote_owner can contain city, depending
|
|
||||||
# on length of total. As there is no clear pattern, leave it as
|
|
||||||
# is.
|
|
||||||
self.message = self.refold_message(self.message)
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Final default: reconstruct message from chopped fixed length
|
|
||||||
# message parts.
|
|
||||||
self.message = self.refold_message(self.message)
|
|
||||||
|
|
||||||
|
|
||||||
class statement(models.mem_bank_statement):
|
|
||||||
'''
|
|
||||||
Implementation of bank_statement communication class of account_banking
|
|
||||||
'''
|
|
||||||
def __init__(self, msg, start_balance=0.0, *args, **kwargs):
|
|
||||||
'''
|
|
||||||
Set decent start values based on first transaction read
|
|
||||||
'''
|
|
||||||
super(statement, self).__init__(*args, **kwargs)
|
|
||||||
self.id = msg.statement_id
|
|
||||||
self.local_account = msg.local_account
|
|
||||||
self.date = msg.date
|
|
||||||
self.start_balance = self.end_balance = start_balance
|
|
||||||
self.import_transaction(msg)
|
|
||||||
|
|
||||||
def import_transaction(self, msg):
|
|
||||||
'''
|
|
||||||
Import a transaction and keep some house holding in the mean time.
|
|
||||||
'''
|
|
||||||
trans = transaction(msg)
|
|
||||||
self.end_balance += trans.transferred_amount
|
|
||||||
self.transactions.append(trans)
|
|
||||||
|
|
||||||
|
|
||||||
class parser(models.parser):
|
|
||||||
code = 'NLGT'
|
|
||||||
name = _('Dutch Girotel - Kommagescheiden')
|
|
||||||
country_code = 'NL'
|
|
||||||
doc = _('''\
|
|
||||||
The Dutch Girotel - Kommagescheiden format is basicly a MS Excel CSV format.
|
|
||||||
''')
|
|
||||||
|
|
||||||
def parse(self, cr, data):
|
|
||||||
result = []
|
|
||||||
stmnt = None
|
|
||||||
dialect = csv.excel()
|
|
||||||
dialect.quotechar = '"'
|
|
||||||
dialect.delimiter = ','
|
|
||||||
lines = data.split('\n')
|
|
||||||
start_balance = 0.0
|
|
||||||
for line in csv.reader(lines, dialect=dialect):
|
|
||||||
# Skip empty (last) lines
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
msg = transaction_message(line)
|
|
||||||
if stmnt and stmnt.id != msg.statement_id:
|
|
||||||
start_balance = stmnt.end_balance
|
|
||||||
result.append(stmnt)
|
|
||||||
stmnt = None
|
|
||||||
if not stmnt:
|
|
||||||
stmnt = statement(msg, start_balance=start_balance)
|
|
||||||
else:
|
|
||||||
stmnt.import_transaction(msg)
|
|
||||||
result.append(stmnt)
|
|
||||||
return result
|
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# Translation of OpenERP Server.
|
|
||||||
# This file contains the translation of the following modules:
|
|
||||||
# * account_banking_nl_girotel
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: OpenERP Server 7.0\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2013-10-25 15:54+0000\n"
|
|
||||||
"PO-Revision-Date: 2013-10-25 15:54+0000\n"
|
|
||||||
"Last-Translator: <>\n"
|
|
||||||
"Language-Team: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: \n"
|
|
||||||
"Plural-Forms: \n"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_girotel
|
|
||||||
#: code:addons/account_banking_nl_girotel/girotel.py:325
|
|
||||||
#, python-format
|
|
||||||
msgid "Dutch Girotel - Kommagescheiden"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_girotel
|
|
||||||
#: code:addons/account_banking_nl_girotel/girotel.py:327
|
|
||||||
#, python-format
|
|
||||||
msgid "The Dutch Girotel - Kommagescheiden format is basicly a MS Excel CSV format.\n"
|
|
||||||
""
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_girotel
|
|
||||||
#: code:addons/account_banking_nl_girotel/girotel.py:103
|
|
||||||
#, python-format
|
|
||||||
msgid "Invalid transaction line: expected %d columns, found %d"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# Translation of OpenERP Server.
|
|
||||||
# This file contains the translation of the following modules:
|
|
||||||
# * account_banking_nl_multibank
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: OpenERP Server 5.0.7\n"
|
|
||||||
"Report-Msgid-Bugs-To: support@openerp.com\n"
|
|
||||||
"POT-Creation-Date: 2013-10-25 15:54+0000\n"
|
|
||||||
"PO-Revision-Date: 2013-11-11 17:50+0000\n"
|
|
||||||
"Last-Translator: <>\n"
|
|
||||||
"Language-Team: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"X-Launchpad-Export-Date: 2014-05-31 06:02+0000\n"
|
|
||||||
"X-Generator: Launchpad (build 17031)\n"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_girotel
|
|
||||||
#: code:addons/account_banking_nl_girotel/girotel.py:325
|
|
||||||
#, python-format
|
|
||||||
msgid "Dutch Girotel - Kommagescheiden"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_girotel
|
|
||||||
#: code:addons/account_banking_nl_girotel/girotel.py:327
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"The Dutch Girotel - Kommagescheiden format is basicly a MS Excel CSV "
|
|
||||||
"format.\n"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_girotel
|
|
||||||
#: code:addons/account_banking_nl_girotel/girotel.py:103
|
|
||||||
#, python-format
|
|
||||||
msgid "Invalid transaction line: expected %d columns, found %d"
|
|
||||||
msgstr ""
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
# Translation of OpenERP Server.
|
|
||||||
# This file contains the translation of the following modules:
|
|
||||||
# * account_banking_nl_girotel
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: OpenERP Server 5.0.12\n"
|
|
||||||
"Report-Msgid-Bugs-To: info@edusense.nl\n"
|
|
||||||
"POT-Creation-Date: 2013-10-25 15:54+0000\n"
|
|
||||||
"PO-Revision-Date: 2013-12-02 14:45+0000\n"
|
|
||||||
"Last-Translator: Jan Jurkus (GCE CAD-Service) <ict@gcecad-service.nl>\n"
|
|
||||||
"Language-Team: Dutch\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"X-Launchpad-Export-Date: 2014-05-31 06:02+0000\n"
|
|
||||||
"X-Generator: Launchpad (build 17031)\n"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_girotel
|
|
||||||
#: code:addons/account_banking_nl_girotel/girotel.py:325
|
|
||||||
#, python-format
|
|
||||||
msgid "Dutch Girotel - Kommagescheiden"
|
|
||||||
msgstr "Nederlands Girotel - Kommagescheiden"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_girotel
|
|
||||||
#: code:addons/account_banking_nl_girotel/girotel.py:327
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"The Dutch Girotel - Kommagescheiden format is basicly a MS Excel CSV "
|
|
||||||
"format.\n"
|
|
||||||
msgstr ""
|
|
||||||
"Het Nederlandse Girotel - kommagescheiden formaat is in opzet een MS Excel "
|
|
||||||
"CSV-formaat.\n"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_girotel
|
|
||||||
#: code:addons/account_banking_nl_girotel/girotel.py:103
|
|
||||||
#, python-format
|
|
||||||
msgid "Invalid transaction line: expected %d columns, found %d"
|
|
||||||
msgstr "Ongeldige transactieregel: %d kolommen verwacht, %d aangetroffen"
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# Brazilian Portuguese translation for banking-addons
|
|
||||||
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
|
|
||||||
# This file is distributed under the same license as the banking-addons package.
|
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: banking-addons\n"
|
|
||||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
|
||||||
"POT-Creation-Date: 2013-10-25 15:54+0000\n"
|
|
||||||
"PO-Revision-Date: 2013-12-25 12:33+0000\n"
|
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
||||||
"Language-Team: Brazilian Portuguese <pt_BR@li.org>\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"X-Launchpad-Export-Date: 2014-05-31 06:02+0000\n"
|
|
||||||
"X-Generator: Launchpad (build 17031)\n"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_girotel
|
|
||||||
#: code:addons/account_banking_nl_girotel/girotel.py:325
|
|
||||||
#, python-format
|
|
||||||
msgid "Dutch Girotel - Kommagescheiden"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_girotel
|
|
||||||
#: code:addons/account_banking_nl_girotel/girotel.py:327
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"The Dutch Girotel - Kommagescheiden format is basicly a MS Excel CSV "
|
|
||||||
"format.\n"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_girotel
|
|
||||||
#: code:addons/account_banking_nl_girotel/girotel.py:103
|
|
||||||
#, python-format
|
|
||||||
msgid "Invalid transaction line: expected %d columns, found %d"
|
|
||||||
msgstr ""
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
import ing
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Copyright (C) 2011 Therp BV (<http://therp.nl>)
|
|
||||||
# (C) 2011 Smile BV (<http://smile.fr>)
|
|
||||||
#
|
|
||||||
# Based on account-banking
|
|
||||||
# (C) 2009 - 2011 EduSense BV (<http://www.edusense.nl>)
|
|
||||||
#
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
{
|
|
||||||
'name': 'ING (NL) Bank Statements Import',
|
|
||||||
'version': '0.1.140',
|
|
||||||
'license': 'AGPL-3',
|
|
||||||
'author': ['Smile', 'Therp BV', 'EduSense BV'],
|
|
||||||
'website': 'https://launchpad.net/banking-addons',
|
|
||||||
'category': 'Banking addons',
|
|
||||||
'depends': ['account_banking'],
|
|
||||||
'description': '''
|
|
||||||
Module to import Dutch ING bank format transaction files. The format covered
|
|
||||||
is the CSV format with either 'dd-mm-yyyy' or 'yyyymmdd' date syntax.
|
|
||||||
|
|
||||||
As the ING bank does not provide detailed specification concerning possible
|
|
||||||
values and their meaning for the fields in the CSV file format, the statements
|
|
||||||
are parsed according to an educated guess based on incomplete information.
|
|
||||||
You can contact the banking-addons developers through their launchpad page and
|
|
||||||
help improve the performance of this import filter on
|
|
||||||
https://launchpad.net/banking-addons.
|
|
||||||
|
|
||||||
Note that imported bank transfers are organized in statements covering periods
|
|
||||||
of one week, even if the imported files cover a different period.
|
|
||||||
|
|
||||||
This modules contains no logic, just an import filter for account_banking.
|
|
||||||
''',
|
|
||||||
'installable': False,
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# Translation of OpenERP Server.
|
|
||||||
# This file contains the translation of the following modules:
|
|
||||||
# * account_banking_nl_ing
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: OpenERP Server 7.0\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2013-10-25 15:55+0000\n"
|
|
||||||
"PO-Revision-Date: 2013-10-25 15:55+0000\n"
|
|
||||||
"Last-Translator: <>\n"
|
|
||||||
"Language-Team: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: \n"
|
|
||||||
"Plural-Forms: \n"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_ing
|
|
||||||
#: code:addons/account_banking_nl_ing/ing.py:257
|
|
||||||
#, python-format
|
|
||||||
msgid "ING Bank"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_ing
|
|
||||||
#: code:addons/account_banking_nl_ing/ing.py:258
|
|
||||||
#, python-format
|
|
||||||
msgid "The Dutch ING format is basicly a MS Excel CSV format. It is specifically\n"
|
|
||||||
"distinct from the Dutch multibank format. Transactions are not tied to Bank\n"
|
|
||||||
"Statements.\n"
|
|
||||||
""
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_ing
|
|
||||||
#: code:addons/account_banking_nl_ing/ing.py:62
|
|
||||||
#, python-format
|
|
||||||
msgid "Invalid transaction line: expected %d columns, found %d"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# Translation of OpenERP Server.
|
|
||||||
# This file contains the translation of the following modules:
|
|
||||||
# * account_banking_nl_ing
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: OpenERP Server 6.0.3\n"
|
|
||||||
"Report-Msgid-Bugs-To: support@openerp.com\n"
|
|
||||||
"POT-Creation-Date: 2013-10-25 15:55+0000\n"
|
|
||||||
"PO-Revision-Date: 2013-12-03 11:10+0000\n"
|
|
||||||
"Last-Translator: Jan Jurkus (GCE CAD-Service) <ict@gcecad-service.nl>\n"
|
|
||||||
"Language-Team: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"X-Launchpad-Export-Date: 2014-05-31 06:02+0000\n"
|
|
||||||
"X-Generator: Launchpad (build 17031)\n"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_ing
|
|
||||||
#: code:addons/account_banking_nl_ing/ing.py:257
|
|
||||||
#, python-format
|
|
||||||
msgid "ING Bank"
|
|
||||||
msgstr "ING Bank"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_ing
|
|
||||||
#: code:addons/account_banking_nl_ing/ing.py:258
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"The Dutch ING format is basicly a MS Excel CSV format. It is specifically\n"
|
|
||||||
"distinct from the Dutch multibank format. Transactions are not tied to Bank\n"
|
|
||||||
"Statements.\n"
|
|
||||||
msgstr ""
|
|
||||||
"Het Nederlandse ING formaat is in opzet een MS Excel CSV-formaat. Het is\n"
|
|
||||||
"duidelijk afwijkend van het Nederlandse multibank-formaat. Transacties zijn\n"
|
|
||||||
"niet gebonden aan bankafschriften.\n"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_ing
|
|
||||||
#: code:addons/account_banking_nl_ing/ing.py:62
|
|
||||||
#, python-format
|
|
||||||
msgid "Invalid transaction line: expected %d columns, found %d"
|
|
||||||
msgstr "Ongeldige transactieregel: %d kolommen verwacht, %d aangetroffen"
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
# Brazilian Portuguese translation for banking-addons
|
|
||||||
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
|
|
||||||
# This file is distributed under the same license as the banking-addons package.
|
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: banking-addons\n"
|
|
||||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
|
||||||
"POT-Creation-Date: 2013-10-25 15:55+0000\n"
|
|
||||||
"PO-Revision-Date: 2013-12-25 12:31+0000\n"
|
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
||||||
"Language-Team: Brazilian Portuguese <pt_BR@li.org>\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"X-Launchpad-Export-Date: 2014-05-31 06:02+0000\n"
|
|
||||||
"X-Generator: Launchpad (build 17031)\n"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_ing
|
|
||||||
#: code:addons/account_banking_nl_ing/ing.py:257
|
|
||||||
#, python-format
|
|
||||||
msgid "ING Bank"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_ing
|
|
||||||
#: code:addons/account_banking_nl_ing/ing.py:258
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"The Dutch ING format is basicly a MS Excel CSV format. It is specifically\n"
|
|
||||||
"distinct from the Dutch multibank format. Transactions are not tied to Bank\n"
|
|
||||||
"Statements.\n"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_ing
|
|
||||||
#: code:addons/account_banking_nl_ing/ing.py:62
|
|
||||||
#, python-format
|
|
||||||
msgid "Invalid transaction line: expected %d columns, found %d"
|
|
||||||
msgstr ""
|
|
||||||
@@ -1,288 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Copyright (C) 2011 Smile (<http://smile.fr>).
|
|
||||||
# Copyright (C) 2011 Therp BV (<http://therp.nl>).
|
|
||||||
#
|
|
||||||
# Based on the multibank module by EduSense BV
|
|
||||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>),
|
|
||||||
#
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
from openerp.addons.account_banking.parsers import models
|
|
||||||
from openerp.addons.account_banking.parsers.convert import str2date
|
|
||||||
from openerp.tools.translate import _
|
|
||||||
|
|
||||||
import re
|
|
||||||
import csv
|
|
||||||
|
|
||||||
__all__ = ['parser']
|
|
||||||
|
|
||||||
bt = models.mem_bank_transaction
|
|
||||||
|
|
||||||
"""
|
|
||||||
First line states the legend
|
|
||||||
"Datum","Naam / Omschrijving","Rekening","Tegenrekening","Code","Af Bij",\
|
|
||||||
"Bedrag (EUR)","MutatieSoort","Mededelingen
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class transaction_message(object):
|
|
||||||
'''
|
|
||||||
A auxiliary class to validate and coerce read values
|
|
||||||
'''
|
|
||||||
attrnames = [
|
|
||||||
'date', 'remote_owner', 'local_account', 'remote_account',
|
|
||||||
'transfer_type', 'debcred', 'transferred_amount',
|
|
||||||
'transfer_type_verbose', 'message'
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, values, subno):
|
|
||||||
'''
|
|
||||||
Initialize own dict with attributes and coerce values to right type
|
|
||||||
'''
|
|
||||||
if len(self.attrnames) != len(values):
|
|
||||||
raise ValueError(
|
|
||||||
_('Invalid transaction line: expected %d columns, found %d')
|
|
||||||
% (len(self.attrnames), len(values)))
|
|
||||||
self.__dict__.update(dict(zip(self.attrnames, values)))
|
|
||||||
# for lack of a standardized locale function to parse amounts
|
|
||||||
self.transferred_amount = float(
|
|
||||||
re.sub(',', '.', self.transferred_amount))
|
|
||||||
if self.debcred == 'Af':
|
|
||||||
self.transferred_amount = -self.transferred_amount
|
|
||||||
try:
|
|
||||||
self.execution_date = self.value_date = str2date(self.date,
|
|
||||||
'%Y%m%d')
|
|
||||||
except ValueError:
|
|
||||||
self.execution_date = self.value_date = str2date(self.date,
|
|
||||||
'%d-%m-%Y')
|
|
||||||
self.statement_id = '' # self.value_date.strftime('%Yw%W')
|
|
||||||
self.id = str(subno).zfill(4)
|
|
||||||
self.reference = ''
|
|
||||||
# Normalize basic account numbers
|
|
||||||
self.remote_account = self.remote_account.replace('.', '').zfill(10)
|
|
||||||
self.local_account = self.local_account.replace('.', '').zfill(10)
|
|
||||||
|
|
||||||
|
|
||||||
class transaction(models.mem_bank_transaction):
|
|
||||||
'''
|
|
||||||
Implementation of transaction communication class for account_banking.
|
|
||||||
'''
|
|
||||||
attrnames = ['local_account', 'remote_account',
|
|
||||||
'remote_owner', 'transferred_amount',
|
|
||||||
'execution_date', 'value_date', 'transfer_type',
|
|
||||||
'id', 'reference', 'statement_id', 'message',
|
|
||||||
]
|
|
||||||
|
|
||||||
"""
|
|
||||||
Presumably the same transaction types occur in the MT940 format of ING.
|
|
||||||
From www.ing.nl/Images/MT940_Technische_handleiding_tcm7-69020.pdf
|
|
||||||
|
|
||||||
"""
|
|
||||||
type_map = {
|
|
||||||
|
|
||||||
'AC': bt.ORDER, # Acceptgiro
|
|
||||||
'BA': bt.PAYMENT_TERMINAL, # Betaalautomaattransactie
|
|
||||||
'CH': bt.ORDER, # Cheque
|
|
||||||
'DV': bt.ORDER, # Diversen
|
|
||||||
'FL': bt.BANK_TERMINAL, # Filiaalboeking, concernboeking
|
|
||||||
'GF': bt.ORDER, # Telefonisch bankieren
|
|
||||||
'GM': bt.BANK_TERMINAL, # Geldautomaat
|
|
||||||
'GT': bt.ORDER, # Internetbankieren
|
|
||||||
'IC': bt.DIRECT_DEBIT, # Incasso
|
|
||||||
'OV': bt.ORDER, # Overschrijving
|
|
||||||
'PK': bt.BANK_TERMINAL, # Opname kantoor
|
|
||||||
'PO': bt.ORDER, # Periodieke overschrijving
|
|
||||||
'ST': bt.BANK_TERMINAL, # Storting (eigen rekening of derde)
|
|
||||||
'VZ': bt.ORDER, # Verzamelbetaling
|
|
||||||
'NO': bt.STORNO, # Storno
|
|
||||||
}
|
|
||||||
|
|
||||||
# global expression for matching storno references
|
|
||||||
ref_expr = re.compile('REF[\*:]([0-9A-Z-z_-]+)')
|
|
||||||
# match references for Acceptgiro's through Internet banking
|
|
||||||
kn_expr = re.compile('KN: ([^ ]+)')
|
|
||||||
|
|
||||||
def __init__(self, line, *args, **kwargs):
|
|
||||||
'''
|
|
||||||
Initialize own dict with read values.
|
|
||||||
'''
|
|
||||||
super(transaction, self).__init__(*args, **kwargs)
|
|
||||||
# Copy attributes from auxiliary class to self.
|
|
||||||
for attr in self.attrnames:
|
|
||||||
setattr(self, attr, getattr(line, attr))
|
|
||||||
# self.message = ''
|
|
||||||
# Decompose structured messages
|
|
||||||
self.parse_message()
|
|
||||||
# Adaptations to direct debit orders ands stornos
|
|
||||||
if self.transfer_type == 'DV' and self.transferred_amount < 0:
|
|
||||||
res = self.ref_expr.search(self.remote_owner)
|
|
||||||
if res:
|
|
||||||
self.transfer_type = 'NO'
|
|
||||||
self.reference = res.group(1)
|
|
||||||
self.remote_owner = False
|
|
||||||
else:
|
|
||||||
res = self.ref_expr.search(self.message)
|
|
||||||
if res:
|
|
||||||
self.transfer_type = 'NO'
|
|
||||||
self.reference = res.group(1)
|
|
||||||
if self.transfer_type == 'IC':
|
|
||||||
if self.transferred_amount > 0:
|
|
||||||
self.reference = self.remote_owner
|
|
||||||
else:
|
|
||||||
self.transfer_type = 'NO'
|
|
||||||
self.message = self.remote_owner + self.message
|
|
||||||
res = self.ref_expr.search(self.message)
|
|
||||||
if res:
|
|
||||||
self.reference = res.group(1)
|
|
||||||
self.storno_retry = True
|
|
||||||
self.remote_owner = False
|
|
||||||
if self.transfer_type == 'GT':
|
|
||||||
res = self.kn_expr.search(self.message)
|
|
||||||
if res:
|
|
||||||
self.reference = res.group(1)
|
|
||||||
if self.transfer_type == 'AC':
|
|
||||||
self.parse_acceptgiro()
|
|
||||||
if self.message and not self.reference:
|
|
||||||
self.reference = self.message
|
|
||||||
|
|
||||||
def parse_acceptgiro(self):
|
|
||||||
"""
|
|
||||||
Entries of type 'Acceptgiro' can contain the reference
|
|
||||||
in the 'name' column, as well as full address information
|
|
||||||
in the 'message' column'
|
|
||||||
"""
|
|
||||||
before = False
|
|
||||||
if self.remote_owner.startswith('KN: '):
|
|
||||||
self.reference = self.remote_owner[4:]
|
|
||||||
self.remote_owner = ''
|
|
||||||
if 'KN: ' in self.message:
|
|
||||||
index = self.message.index('KN: ')
|
|
||||||
before = self.message[:index]
|
|
||||||
self.message = self.message[index:]
|
|
||||||
expression = (
|
|
||||||
"^\s*(KN:\s*(?P<kn>[^\s]+))?(\s*)"
|
|
||||||
"(?P<navr>NAVR:\s*[^\s]+)?(\s*)(?P<after>.*?)$")
|
|
||||||
msg_match = re.match(expression, self.message)
|
|
||||||
after = msg_match.group('after')
|
|
||||||
kn = msg_match.group('kn')
|
|
||||||
navr = msg_match.group('navr')
|
|
||||||
if kn:
|
|
||||||
self.reference = kn[4:]
|
|
||||||
self.message = 'Acceptgiro %s' % (navr or '')
|
|
||||||
if after:
|
|
||||||
parts = [after[i:i+33] for i in range(0, len(after), 33)]
|
|
||||||
if parts and not self.remote_owner:
|
|
||||||
self.remote_owner = parts.pop(0).strip()
|
|
||||||
if parts:
|
|
||||||
self.remote_owner_address = [parts.pop(0).strip()]
|
|
||||||
if parts:
|
|
||||||
zip_city = parts.pop(0).strip()
|
|
||||||
zip_match = re.match(
|
|
||||||
"^(?P<zipcode>[^ ]{6})\s+(?P<city>.*?)$", zip_city)
|
|
||||||
if zip_match:
|
|
||||||
self.remote_owner_postalcode = zip_match.group('zipcode')
|
|
||||||
self.remote_owner_city = zip_match.group('city')
|
|
||||||
if before and not self.remote_owner_city:
|
|
||||||
self.remote_owner_city = before.strip()
|
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
if not self.error_message:
|
|
||||||
if not self.transferred_amount:
|
|
||||||
self.error_message = "No transferred amount"
|
|
||||||
elif not self.execution_date:
|
|
||||||
self.error_message = "No execution date"
|
|
||||||
elif not self.remote_account and self.transfer_type not in [
|
|
||||||
'BA', 'FL', 'GM', 'IC', 'PK', 'ST']:
|
|
||||||
self.error_message = (
|
|
||||||
"No remote account for transaction type %s" %
|
|
||||||
self.transfer_type)
|
|
||||||
return not self.error_message
|
|
||||||
|
|
||||||
def parse_message(self):
|
|
||||||
'''
|
|
||||||
Parse structured message parts into appropriate attributes.
|
|
||||||
No processing done here for Triodos, maybe later.
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
class statement(models.mem_bank_statement):
|
|
||||||
'''
|
|
||||||
Implementation of bank_statement communication class of account_banking
|
|
||||||
'''
|
|
||||||
def __init__(self, msg, *args, **kwargs):
|
|
||||||
'''
|
|
||||||
Set decent start values based on first transaction read
|
|
||||||
'''
|
|
||||||
super(statement, self).__init__(*args, **kwargs)
|
|
||||||
self.id = msg.statement_id
|
|
||||||
self.local_account = msg.local_account
|
|
||||||
try:
|
|
||||||
self.date = str2date(msg.date, '%Y%m%d')
|
|
||||||
except ValueError:
|
|
||||||
self.date = str2date(msg.date, '%d-%m-%Y')
|
|
||||||
self.start_balance = self.end_balance = 0 # msg.start_balance
|
|
||||||
self.import_transaction(msg)
|
|
||||||
|
|
||||||
def import_transaction(self, msg):
|
|
||||||
'''
|
|
||||||
Import a transaction and keep some house holding in the mean time.
|
|
||||||
'''
|
|
||||||
trans = transaction(msg)
|
|
||||||
self.end_balance += trans.transferred_amount
|
|
||||||
self.transactions.append(trans)
|
|
||||||
|
|
||||||
|
|
||||||
class parser(models.parser):
|
|
||||||
code = 'ING'
|
|
||||||
country_code = 'NL'
|
|
||||||
name = _('ING Bank')
|
|
||||||
doc = _('''\
|
|
||||||
The Dutch ING format is basicly a MS Excel CSV format. It is specifically
|
|
||||||
distinct from the Dutch multibank format. Transactions are not tied to Bank
|
|
||||||
Statements.
|
|
||||||
''')
|
|
||||||
|
|
||||||
def parse(self, cr, data):
|
|
||||||
result = []
|
|
||||||
stmnt = None
|
|
||||||
dialect = csv.excel()
|
|
||||||
dialect.quotechar = '"'
|
|
||||||
dialect.delimiter = ','
|
|
||||||
lines = data.split('\n')
|
|
||||||
# Transaction lines are not numbered, so keep a tracer
|
|
||||||
subno = 0
|
|
||||||
statement_id = False
|
|
||||||
for line in csv.reader(lines, dialect=dialect):
|
|
||||||
# Skip empty (last) lines and header line
|
|
||||||
if not line or line[0] == 'Datum':
|
|
||||||
continue
|
|
||||||
subno += 1
|
|
||||||
msg = transaction_message(line, subno)
|
|
||||||
if not statement_id:
|
|
||||||
statement_id = self.get_unique_statement_id(
|
|
||||||
cr, msg.execution_date.strftime('%Yw%W'))
|
|
||||||
msg.statement_id = statement_id
|
|
||||||
if stmnt:
|
|
||||||
stmnt.import_transaction(msg)
|
|
||||||
else:
|
|
||||||
stmnt = statement(msg)
|
|
||||||
result.append(stmnt)
|
|
||||||
return result
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# OpenERP, Open Source Management Solution
|
|
||||||
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
|
|
||||||
#
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
from . import account_banking_nl_ing_mt940
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# OpenERP, Open Source Management Solution
|
|
||||||
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
|
|
||||||
#
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
{
|
|
||||||
"name": "MT940 import for Dutch ING",
|
|
||||||
"version": "1.1",
|
|
||||||
"author": "Therp BV",
|
|
||||||
"complexity": "normal",
|
|
||||||
"description": """
|
|
||||||
This addon imports the structured MT940 format as offered by the Dutch ING
|
|
||||||
bank.
|
|
||||||
""",
|
|
||||||
"category": "Account Banking",
|
|
||||||
"depends": [
|
|
||||||
'account_banking_mt940',
|
|
||||||
],
|
|
||||||
"data": [
|
|
||||||
],
|
|
||||||
"js": [
|
|
||||||
],
|
|
||||||
"css": [
|
|
||||||
],
|
|
||||||
"qweb": [
|
|
||||||
],
|
|
||||||
"auto_install": False,
|
|
||||||
'installable': False,
|
|
||||||
"application": False,
|
|
||||||
"external_dependencies": {
|
|
||||||
'python': [],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# OpenERP, Open Source Management Solution
|
|
||||||
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
|
|
||||||
#
|
|
||||||
# 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 re
|
|
||||||
from openerp.tools.translate import _
|
|
||||||
from openerp.addons.account_banking.parsers.models import (
|
|
||||||
parser,
|
|
||||||
mem_bank_transaction,
|
|
||||||
)
|
|
||||||
from openerp.addons.account_banking_mt940.mt940 import MT940, str2float
|
|
||||||
|
|
||||||
|
|
||||||
class transaction(mem_bank_transaction):
|
|
||||||
def is_valid(self):
|
|
||||||
'''allow transactions without remote account'''
|
|
||||||
return bool(self.execution_date) and bool(self.transferred_amount)
|
|
||||||
|
|
||||||
|
|
||||||
class IngMT940Parser(MT940, parser):
|
|
||||||
name = _('ING MT940 (structured)')
|
|
||||||
country_code = 'NL'
|
|
||||||
code = 'INT_MT940_STRUC'
|
|
||||||
|
|
||||||
tag_61_regex = re.compile(
|
|
||||||
'^(?P<date>\d{6})(?P<sign>[CD])(?P<amount>\d+,\d{2})N(?P<type>.{3})'
|
|
||||||
'(?P<reference>\w{1,16})')
|
|
||||||
|
|
||||||
def create_transaction(self, cr):
|
|
||||||
return transaction()
|
|
||||||
|
|
||||||
def handle_tag_60F(self, cr, data):
|
|
||||||
super(IngMT940Parser, self).handle_tag_60F(cr, data)
|
|
||||||
self.current_statement.id = '%s-%s' % (
|
|
||||||
self.get_unique_account_identifier(
|
|
||||||
cr, self.current_statement.local_account),
|
|
||||||
self.current_statement.id)
|
|
||||||
|
|
||||||
def handle_tag_61(self, cr, data):
|
|
||||||
super(IngMT940Parser, self).handle_tag_61(cr, data)
|
|
||||||
parsed_data = self.tag_61_regex.match(data).groupdict()
|
|
||||||
self.current_transaction.transferred_amount = \
|
|
||||||
(-1 if parsed_data['sign'] == 'D' else 1) * str2float(
|
|
||||||
parsed_data['amount'])
|
|
||||||
self.current_transaction.reference = parsed_data['reference']
|
|
||||||
|
|
||||||
def handle_tag_86(self, cr, data):
|
|
||||||
if not self.current_transaction:
|
|
||||||
return
|
|
||||||
super(IngMT940Parser, self).handle_tag_86(cr, data)
|
|
||||||
codewords = ['RTRN', 'BENM', 'ORDP', 'CSID', 'BUSP', 'MARF', 'EREF',
|
|
||||||
'PREF', 'REMI', 'ID', 'PURP', 'ULTB', 'ULTD',
|
|
||||||
'CREF', 'IREF', 'CNTP', 'ULTC', 'EXCH', 'CHGS']
|
|
||||||
subfields = {}
|
|
||||||
current_codeword = None
|
|
||||||
for word in data.split('/'):
|
|
||||||
if not word and not current_codeword:
|
|
||||||
continue
|
|
||||||
if word in codewords:
|
|
||||||
current_codeword = word
|
|
||||||
subfields[current_codeword] = []
|
|
||||||
continue
|
|
||||||
if current_codeword in subfields:
|
|
||||||
subfields[current_codeword].append(word)
|
|
||||||
|
|
||||||
if 'CNTP' in subfields:
|
|
||||||
self.current_transaction.remote_account = subfields['CNTP'][0]
|
|
||||||
self.current_transaction.remote_bank_bic = subfields['CNTP'][1]
|
|
||||||
self.current_transaction.remote_owner = subfields['CNTP'][2]
|
|
||||||
self.current_transaction.remote_owner_city = subfields['CNTP'][3]
|
|
||||||
|
|
||||||
if 'BENM' in subfields:
|
|
||||||
self.current_transaction.remote_account = subfields['BENM'][0]
|
|
||||||
self.current_transaction.remote_bank_bic = subfields['BENM'][1]
|
|
||||||
self.current_transaction.remote_owner = subfields['BENM'][2]
|
|
||||||
self.current_transaction.remote_owner_city = subfields['BENM'][3]
|
|
||||||
|
|
||||||
if 'ORDP' in subfields:
|
|
||||||
self.current_transaction.remote_account = subfields['ORDP'][0]
|
|
||||||
self.current_transaction.remote_bank_bic = subfields['ORDP'][1]
|
|
||||||
self.current_transaction.remote_owner = subfields['ORDP'][2]
|
|
||||||
self.current_transaction.remote_owner_city = subfields['ORDP'][3]
|
|
||||||
|
|
||||||
if 'REMI' in subfields:
|
|
||||||
self.current_transaction.message = '/'.join(
|
|
||||||
filter(lambda x: bool(x), subfields['REMI']))
|
|
||||||
|
|
||||||
if self.current_transaction.reference in subfields:
|
|
||||||
self.current_transaction.reference = ''.join(
|
|
||||||
subfields[self.current_transaction.reference])
|
|
||||||
|
|
||||||
if not subfields:
|
|
||||||
self.current_transaction.message = data
|
|
||||||
|
|
||||||
self.current_transaction = None
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
|
||||||
# All Rights Reserved
|
|
||||||
#
|
|
||||||
# WARNING: This program as such is intended to be used by professional
|
|
||||||
# programmers who take the whole responsability of assessing all potential
|
|
||||||
# consequences resulting from its eventual inadequacies and bugs
|
|
||||||
# End users who are looking for a ready-to-use solution with commercial
|
|
||||||
# garantees and support are strongly adviced to contract EduSense BV
|
|
||||||
#
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
from . import multibank
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
{
|
|
||||||
'name': 'Account Banking - NL Multibank import',
|
|
||||||
'version': '0.62',
|
|
||||||
'license': 'AGPL-3',
|
|
||||||
'author': 'EduSense BV',
|
|
||||||
'website': 'http://www.edusense.nl',
|
|
||||||
'category': 'Account Banking',
|
|
||||||
'depends': ['account_banking'],
|
|
||||||
'description': '''
|
|
||||||
Module to import Dutch Multibank format transation files.
|
|
||||||
|
|
||||||
This modules contains no logic, just an import filter for account_banking.
|
|
||||||
''',
|
|
||||||
'installable': False,
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
# Translation of OpenERP Server.
|
|
||||||
# This file contains the translation of the following modules:
|
|
||||||
# * account_banking_nl_multibank
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: OpenERP Server 7.0\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2013-10-25 15:56+0000\n"
|
|
||||||
"PO-Revision-Date: 2013-10-25 15:56+0000\n"
|
|
||||||
"Last-Translator: <>\n"
|
|
||||||
"Language-Team: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: \n"
|
|
||||||
"Plural-Forms: \n"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_multibank
|
|
||||||
#: code:addons/account_banking_nl_multibank/multibank.py:292
|
|
||||||
#, python-format
|
|
||||||
msgid "The Dutch Banking Tools format is basicly a MS Excel CSV format.\n"
|
|
||||||
"There are two sub formats: MS Excel format and MS-Excel 2004 format.\n"
|
|
||||||
"Both formats are covered with this parser. All transactions are tied\n"
|
|
||||||
"to Bank Statements.\n"
|
|
||||||
""
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_multibank
|
|
||||||
#: code:addons/account_banking_nl_multibank/multibank.py:291
|
|
||||||
#, python-format
|
|
||||||
msgid "Dutch Banking Tools"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_multibank
|
|
||||||
#: code:addons/account_banking_nl_multibank/multibank.py:77
|
|
||||||
#, python-format
|
|
||||||
msgid "Invalid transaction line: expected %d columns, found %d"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
# Translation of OpenERP Server.
|
|
||||||
# This file contains the translation of the following modules:
|
|
||||||
# * account_banking_nl_multibank
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: OpenERP Server 5.0.7\n"
|
|
||||||
"Report-Msgid-Bugs-To: support@openerp.com\n"
|
|
||||||
"POT-Creation-Date: 2013-10-25 15:56+0000\n"
|
|
||||||
"PO-Revision-Date: 2013-11-11 17:50+0000\n"
|
|
||||||
"Last-Translator: <>\n"
|
|
||||||
"Language-Team: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"X-Launchpad-Export-Date: 2014-05-31 06:02+0000\n"
|
|
||||||
"X-Generator: Launchpad (build 17031)\n"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_multibank
|
|
||||||
#: code:addons/account_banking_nl_multibank/multibank.py:292
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"The Dutch Banking Tools format is basicly a MS Excel CSV format.\n"
|
|
||||||
"There are two sub formats: MS Excel format and MS-Excel 2004 format.\n"
|
|
||||||
"Both formats are covered with this parser. All transactions are tied\n"
|
|
||||||
"to Bank Statements.\n"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_multibank
|
|
||||||
#: code:addons/account_banking_nl_multibank/multibank.py:291
|
|
||||||
#, python-format
|
|
||||||
msgid "Dutch Banking Tools"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_multibank
|
|
||||||
#: code:addons/account_banking_nl_multibank/multibank.py:77
|
|
||||||
#, python-format
|
|
||||||
msgid "Invalid transaction line: expected %d columns, found %d"
|
|
||||||
msgstr ""
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
# Translation of OpenERP Server.
|
|
||||||
# This file contains the translation of the following modules:
|
|
||||||
# * account_banking_nl_multibank
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: OpenERP Server 5.0.7\n"
|
|
||||||
"Report-Msgid-Bugs-To: support@openerp.com\n"
|
|
||||||
"POT-Creation-Date: 2013-10-25 15:56+0000\n"
|
|
||||||
"PO-Revision-Date: 2013-12-03 11:10+0000\n"
|
|
||||||
"Last-Translator: Jan Jurkus (GCE CAD-Service) <ict@gcecad-service.nl>\n"
|
|
||||||
"Language-Team: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"X-Launchpad-Export-Date: 2014-05-31 06:02+0000\n"
|
|
||||||
"X-Generator: Launchpad (build 17031)\n"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_multibank
|
|
||||||
#: code:addons/account_banking_nl_multibank/multibank.py:292
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"The Dutch Banking Tools format is basicly a MS Excel CSV format.\n"
|
|
||||||
"There are two sub formats: MS Excel format and MS-Excel 2004 format.\n"
|
|
||||||
"Both formats are covered with this parser. All transactions are tied\n"
|
|
||||||
"to Bank Statements.\n"
|
|
||||||
msgstr ""
|
|
||||||
"Het Nederlandse Multibank-formaat is in opzet een MS Excel CSV-formaat.\n"
|
|
||||||
"Er zijn twee subformaten: MS Excel formaat en MS Excel 2004 formaat.\n"
|
|
||||||
"Beide formaten worden ondersteund met deze inlezer. Alle transacties zijn\n"
|
|
||||||
"gekoppeld aan bankafschriften\n"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_multibank
|
|
||||||
#: code:addons/account_banking_nl_multibank/multibank.py:291
|
|
||||||
#, python-format
|
|
||||||
msgid "Dutch Banking Tools"
|
|
||||||
msgstr "Nederlands MultiBank-formaat"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_multibank
|
|
||||||
#: code:addons/account_banking_nl_multibank/multibank.py:77
|
|
||||||
#, python-format
|
|
||||||
msgid "Invalid transaction line: expected %d columns, found %d"
|
|
||||||
msgstr "Ongeldige transactieregel: %d kolommen verwacht, %d aangetroffen"
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
# Brazilian Portuguese translation for banking-addons
|
|
||||||
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
|
|
||||||
# This file is distributed under the same license as the banking-addons package.
|
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: banking-addons\n"
|
|
||||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
|
||||||
"POT-Creation-Date: 2013-10-25 15:56+0000\n"
|
|
||||||
"PO-Revision-Date: 2013-12-25 12:30+0000\n"
|
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
||||||
"Language-Team: Brazilian Portuguese <pt_BR@li.org>\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"X-Launchpad-Export-Date: 2014-05-31 06:02+0000\n"
|
|
||||||
"X-Generator: Launchpad (build 17031)\n"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_multibank
|
|
||||||
#: code:addons/account_banking_nl_multibank/multibank.py:292
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"The Dutch Banking Tools format is basicly a MS Excel CSV format.\n"
|
|
||||||
"There are two sub formats: MS Excel format and MS-Excel 2004 format.\n"
|
|
||||||
"Both formats are covered with this parser. All transactions are tied\n"
|
|
||||||
"to Bank Statements.\n"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_multibank
|
|
||||||
#: code:addons/account_banking_nl_multibank/multibank.py:291
|
|
||||||
#, python-format
|
|
||||||
msgid "Dutch Banking Tools"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_multibank
|
|
||||||
#: code:addons/account_banking_nl_multibank/multibank.py:77
|
|
||||||
#, python-format
|
|
||||||
msgid "Invalid transaction line: expected %d columns, found %d"
|
|
||||||
msgstr ""
|
|
||||||
@@ -1,333 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
'''
|
|
||||||
This parser follows the Dutch Banking Tools specifications which are
|
|
||||||
empirically recreated in this module.
|
|
||||||
|
|
||||||
Dutch Banking Tools uses the concept of 'Afschrift' or Bank Statement.
|
|
||||||
Every transaction is bound to a Bank Statement. As such, this module generates
|
|
||||||
Bank Statements along with Bank Transactions.
|
|
||||||
'''
|
|
||||||
from account_banking.parsers import models
|
|
||||||
from account_banking.parsers.convert import str2date
|
|
||||||
from account_banking.sepa import postalcode
|
|
||||||
from tools.translate import _
|
|
||||||
import csv
|
|
||||||
|
|
||||||
__all__ = ['parser']
|
|
||||||
|
|
||||||
bt = models.mem_bank_transaction
|
|
||||||
|
|
||||||
|
|
||||||
class transaction_message(object):
|
|
||||||
'''
|
|
||||||
A auxiliary class to validate and coerce read values
|
|
||||||
'''
|
|
||||||
attrnames = [
|
|
||||||
'date', 'local_account', 'remote_account', 'remote_owner', 'u1', 'u2',
|
|
||||||
'u3', 'local_currency', 'start_balance', 'remote_currency',
|
|
||||||
'transferred_amount', 'execution_date', 'value_date', 'nr1',
|
|
||||||
'transfer_type', 'nr2', 'reference', 'message', 'statement_id'
|
|
||||||
]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def clean_account(accountno):
|
|
||||||
'''
|
|
||||||
There seems to be some SEPA movement in data: account numbers
|
|
||||||
get prefixed by zeroes as in BBAN. Convert those to 'old' local
|
|
||||||
account numbers
|
|
||||||
|
|
||||||
Edit: All account number now follow the BBAN scheme. As SNS Bank,
|
|
||||||
from which this module was re-engineered, follows the Dutch
|
|
||||||
Banking Tools regulations, it is considered to be used by all banks
|
|
||||||
in the Netherlands which comply to it. If not, please notify us.
|
|
||||||
'''
|
|
||||||
if len(accountno) == 10: # Invalid: longest number is 9
|
|
||||||
accountno = accountno[1:]
|
|
||||||
# 9-scheme or 7-scheme?
|
|
||||||
stripped = accountno.lstrip('0')
|
|
||||||
if len(stripped) <= 7:
|
|
||||||
accountno = stripped
|
|
||||||
return accountno
|
|
||||||
|
|
||||||
def __init__(self, values, subno):
|
|
||||||
'''
|
|
||||||
Initialize own dict with attributes and coerce values to right type
|
|
||||||
'''
|
|
||||||
if len(self.attrnames) != len(values):
|
|
||||||
raise ValueError(
|
|
||||||
_('Invalid transaction line: expected %d columns, found %d')
|
|
||||||
% (len(self.attrnames), len(values))
|
|
||||||
)
|
|
||||||
self.__dict__.update(dict(zip(self.attrnames, values)))
|
|
||||||
self.start_balance = float(self.start_balance)
|
|
||||||
self.transferred_amount = float(self.transferred_amount)
|
|
||||||
self.execution_date = str2date(self.execution_date, '%d-%m-%Y')
|
|
||||||
self.value_date = str2date(self.value_date, '%d-%m-%Y')
|
|
||||||
self.id = str(subno).zfill(4)
|
|
||||||
|
|
||||||
|
|
||||||
class transaction(models.mem_bank_transaction):
|
|
||||||
'''
|
|
||||||
Implementation of transaction communication class for account_banking.
|
|
||||||
'''
|
|
||||||
attrnames = ['local_account', 'local_currency', 'remote_account',
|
|
||||||
'remote_owner', 'remote_currency', 'transferred_amount',
|
|
||||||
'execution_date', 'value_date', 'transfer_type',
|
|
||||||
'reference', 'message', 'statement_id', 'id',
|
|
||||||
]
|
|
||||||
|
|
||||||
type_map = {
|
|
||||||
'ACC': bt.ORDER,
|
|
||||||
'BEA': bt.PAYMENT_TERMINAL,
|
|
||||||
'BTL': bt.ORDER,
|
|
||||||
'DIV': bt.ORDER,
|
|
||||||
'IDB': bt.PAYMENT_TERMINAL,
|
|
||||||
'INC': bt.DIRECT_DEBIT,
|
|
||||||
'IOB': bt.ORDER,
|
|
||||||
'KNT': bt.BANK_COSTS,
|
|
||||||
'KST': bt.BANK_COSTS,
|
|
||||||
'OPN': bt.BANK_TERMINAL,
|
|
||||||
'OVS': bt.ORDER,
|
|
||||||
'PRV': bt.BANK_COSTS,
|
|
||||||
'TEL': bt.ORDER,
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, line, *args, **kwargs):
|
|
||||||
'''
|
|
||||||
Initialize own dict with read values.
|
|
||||||
'''
|
|
||||||
super(transaction, self).__init__(*args, **kwargs)
|
|
||||||
# Copy attributes from auxiliary class to self.
|
|
||||||
for attr in self.attrnames:
|
|
||||||
setattr(self, attr, getattr(line, attr))
|
|
||||||
# Decompose structured messages
|
|
||||||
self.parse_message()
|
|
||||||
# Set reference when bank costs
|
|
||||||
if self.type == bt.BANK_COSTS:
|
|
||||||
self.reference = self.message[:32].rstrip()
|
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
'''
|
|
||||||
There are a few situations that can be signaled as 'invalid' but are
|
|
||||||
valid nontheless:
|
|
||||||
|
|
||||||
1. Transfers from one account to another under the same account holder
|
|
||||||
get not always a remote_account and remote_owner. They have their
|
|
||||||
transfer_type set to 'PRV'.
|
|
||||||
|
|
||||||
2. Invoices from the bank itself are communicated through statements.
|
|
||||||
These too have no remote_account and no remote_owner. They have a
|
|
||||||
transfer_type set to 'KST', 'KNT' or 'DIV'.
|
|
||||||
|
|
||||||
3. Transfers sent through the 'International Transfers' system get
|
|
||||||
their feedback rerouted through a statement, which is not designed to
|
|
||||||
hold the extra fields needed. These transfers have their transfer_type
|
|
||||||
set to 'BTL'.
|
|
||||||
|
|
||||||
4. Cash payments with debit cards are not seen as a transfer between
|
|
||||||
accounts, but as a cash withdrawal. These withdrawals have their
|
|
||||||
transfer_type set to 'BEA'.
|
|
||||||
|
|
||||||
5. Cash withdrawals from banks are too not seen as a transfer between
|
|
||||||
two accounts - the cash exits the banking system. These withdrawals
|
|
||||||
have their transfer_type set to 'OPN'.
|
|
||||||
'''
|
|
||||||
return ((
|
|
||||||
self.transferred_amount and self.execution_date
|
|
||||||
and self.value_date)
|
|
||||||
and (self.remote_account or self.transfer_type in [
|
|
||||||
'KST', 'PRV', 'BTL', 'BEA', 'OPN', 'KNT', 'DIV'
|
|
||||||
] and not self.error_message))
|
|
||||||
|
|
||||||
def parse_message(self):
|
|
||||||
'''
|
|
||||||
Parse structured message parts into appropriate attributes
|
|
||||||
'''
|
|
||||||
if self.transfer_type == 'ACC':
|
|
||||||
# Accept Giro - structured message payment
|
|
||||||
# First part of message is redundant information - strip it
|
|
||||||
msg = self.message[self.message.index('navraagnr.'):]
|
|
||||||
self.message = ' '.join(msg.split())
|
|
||||||
|
|
||||||
elif self.transfer_type == 'BEA':
|
|
||||||
# Payment through payment terminal
|
|
||||||
# Remote owner is part of message, while remote_owner is set
|
|
||||||
# to the intermediate party, which we don't need.
|
|
||||||
self.remote_owner = self.message[:23].rstrip()
|
|
||||||
self.remote_owner_city = self.message[23:31].rstrip()
|
|
||||||
self.message = self.message[31:]
|
|
||||||
|
|
||||||
elif self.transfer_type == 'BTL':
|
|
||||||
# International transfers.
|
|
||||||
# Remote party is encoded in message, including bank costs
|
|
||||||
parts = self.message.split(' ')
|
|
||||||
last = False
|
|
||||||
for part in parts:
|
|
||||||
if part.startswith('bedrag. '):
|
|
||||||
# The ordered transferred amount
|
|
||||||
currency, amount = part.split('. ')[1].split()
|
|
||||||
if self.remote_currency != currency.upper():
|
|
||||||
self.error_message = (
|
|
||||||
'Remote currency in message differs from '
|
|
||||||
'transaction.'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.local_amount = float(amount)
|
|
||||||
elif part.startswith('koers. '):
|
|
||||||
# The currency rate used
|
|
||||||
self.exchange_rate = float(part.split('. ')[1])
|
|
||||||
elif part.startswith('transfer prov. '):
|
|
||||||
# The provision taken by the bank
|
|
||||||
# Note that the amount must be negated to get the right
|
|
||||||
# direction
|
|
||||||
currency, costs = part.split('. ')[1].split()
|
|
||||||
self.provision_costs = -float(costs)
|
|
||||||
self.provision_costs_currency = currency.upper()
|
|
||||||
self.provision_costs_description = 'Transfer costs'
|
|
||||||
elif part.startswith('aan. '):
|
|
||||||
# The remote owner
|
|
||||||
self.remote_owner = part.replace('aan. ', '').rstrip()
|
|
||||||
last = True
|
|
||||||
elif last:
|
|
||||||
# Last parts are address lines
|
|
||||||
address = part.rstrip()
|
|
||||||
iso, pc, city = postalcode.split(address)
|
|
||||||
if pc and city:
|
|
||||||
self.remote_owner_postalcode = pc
|
|
||||||
self.remote_owner_city = city.strip()
|
|
||||||
self.remote_owner_country_code = iso
|
|
||||||
else:
|
|
||||||
self.remote_owner_address.append(address)
|
|
||||||
|
|
||||||
elif self.transfer_type == 'DIV':
|
|
||||||
# A diverse transfer. Message can be anything, but has some
|
|
||||||
# structure
|
|
||||||
ptr = self.message.find(self.reference)
|
|
||||||
if ptr > 0:
|
|
||||||
address = self.message[:ptr].rstrip().split(' ')
|
|
||||||
length = len(address)
|
|
||||||
if length >= 1:
|
|
||||||
self.remote_owner = address[0]
|
|
||||||
if length >= 2:
|
|
||||||
self.remote_owner_address.append(address[1])
|
|
||||||
if length >= 3:
|
|
||||||
self.remote_owner_city = address[2]
|
|
||||||
self.message = self.message[ptr:].rstrip()
|
|
||||||
if self.message.find('transactiedatum') >= 0:
|
|
||||||
rest = self.message.split('transactiedatum')
|
|
||||||
if rest[1].startswith('* '):
|
|
||||||
self.execution_date = str2date(rest[1][2:], '%d-%m-%Y')
|
|
||||||
else:
|
|
||||||
self.execution_date = str2date(rest[1][2:], '%d %m %Y')
|
|
||||||
self.message = rest[0].rstrip()
|
|
||||||
|
|
||||||
elif self.transfer_type == 'IDB':
|
|
||||||
# Payment by iDeal transaction
|
|
||||||
# Remote owner can be part of message, while remote_owner can be
|
|
||||||
# set to the intermediate party, which we don't need.
|
|
||||||
parts = self.message.split(' ')
|
|
||||||
# Second part: structured id, date & time
|
|
||||||
subparts = parts[1].split()
|
|
||||||
datestr = '-'.join(subparts[1:4])
|
|
||||||
timestr = ':'.join(subparts[4:])
|
|
||||||
parts[1] = ' '.join([subparts[0], datestr, timestr])
|
|
||||||
# Only replace reference when redundant
|
|
||||||
if self.reference == parts[0]:
|
|
||||||
if parts[2]:
|
|
||||||
self.reference = ' '.join([parts[2], datestr, timestr])
|
|
||||||
else:
|
|
||||||
self.reference += ' '.join([datestr, timestr])
|
|
||||||
# Optional fourth path contains remote owners name
|
|
||||||
if len(parts) > 3 and parts[-1].find(self.remote_owner) < 0:
|
|
||||||
self.remote_owner = parts[-1].rstrip()
|
|
||||||
parts = parts[:-1]
|
|
||||||
self.message = ' '.join(parts)
|
|
||||||
|
|
||||||
|
|
||||||
class statement(models.mem_bank_statement):
|
|
||||||
'''
|
|
||||||
Implementation of bank_statement communication class of account_banking
|
|
||||||
'''
|
|
||||||
def __init__(self, msg, *args, **kwargs):
|
|
||||||
'''
|
|
||||||
Set decent start values based on first transaction read
|
|
||||||
'''
|
|
||||||
super(statement, self).__init__(*args, **kwargs)
|
|
||||||
self.id = msg.statement_id
|
|
||||||
self.local_account = msg.local_account
|
|
||||||
self.date = str2date(msg.date, '%d-%m-%Y')
|
|
||||||
self.start_balance = self.end_balance = msg.start_balance
|
|
||||||
self.import_transaction(msg)
|
|
||||||
|
|
||||||
def import_transaction(self, msg):
|
|
||||||
'''
|
|
||||||
Import a transaction and keep some house holding in the mean time.
|
|
||||||
'''
|
|
||||||
trans = transaction(msg)
|
|
||||||
self.end_balance += trans.transferred_amount
|
|
||||||
self.transactions.append(trans)
|
|
||||||
|
|
||||||
|
|
||||||
class parser(models.parser):
|
|
||||||
code = 'NLBT'
|
|
||||||
country_code = 'NL'
|
|
||||||
name = _('Dutch Banking Tools')
|
|
||||||
doc = _('''\
|
|
||||||
The Dutch Banking Tools format is basicly a MS Excel CSV format.
|
|
||||||
There are two sub formats: MS Excel format and MS-Excel 2004 format.
|
|
||||||
Both formats are covered with this parser. All transactions are tied
|
|
||||||
to Bank Statements.
|
|
||||||
''')
|
|
||||||
|
|
||||||
def parse(self, cr, data):
|
|
||||||
result = []
|
|
||||||
stmnt = None
|
|
||||||
dialect = csv.excel()
|
|
||||||
dialect.quotechar = '"'
|
|
||||||
dialect.delimiter = ';'
|
|
||||||
lines = data.split('\n')
|
|
||||||
# Probe first record to find out which format we are parsing.
|
|
||||||
if lines and lines[0].count(',') > lines[0].count(';'):
|
|
||||||
dialect.delimiter = ','
|
|
||||||
if lines and lines[0].count("'") > lines[0].count('"'):
|
|
||||||
dialect.quotechar = "'"
|
|
||||||
# Transaction lines are not numbered, so keep a tracer
|
|
||||||
subno = 0
|
|
||||||
for line in csv.reader(lines, dialect=dialect):
|
|
||||||
# Skip empty (last) lines
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
subno += 1
|
|
||||||
msg = transaction_message(line, subno)
|
|
||||||
if stmnt and stmnt.id != msg.statement_id:
|
|
||||||
result.append(stmnt)
|
|
||||||
stmnt = None
|
|
||||||
subno = 0
|
|
||||||
if not stmnt:
|
|
||||||
stmnt = statement(msg)
|
|
||||||
else:
|
|
||||||
stmnt.import_transaction(msg)
|
|
||||||
result.append(stmnt)
|
|
||||||
return result
|
|
||||||
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Copyright (C) 2009 - 2011 EduSense BV (<http://www.edusense.nl>)
|
|
||||||
# and Therp BV (<http://therp.nl>)
|
|
||||||
# All Rights Reserved
|
|
||||||
#
|
|
||||||
# WARNING: This program as such is intended to be used by professional
|
|
||||||
# programmers who take the whole responsability of assessing all potential
|
|
||||||
# consequences resulting from its eventual inadequacies and bugs
|
|
||||||
# End users who are looking for a ready-to-use solution with commercial
|
|
||||||
# garantees and support are strongly adviced to contract EduSense BV
|
|
||||||
# or Therp BV
|
|
||||||
#
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
from . import triodos
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Copyright (C) 2009 - 2011 EduSense BV (<http://www.edusense.nl>)
|
|
||||||
# and Therp BV (<http://therp.nl>)
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
{
|
|
||||||
'name': 'Triodos (NL) Bank Statements Import',
|
|
||||||
'version': '0.92',
|
|
||||||
'license': 'AGPL-3',
|
|
||||||
'author': ['Therp BV', 'EduSense BV'],
|
|
||||||
'website': 'https://launchpad.net/account-banking',
|
|
||||||
'category': 'Account Banking',
|
|
||||||
'depends': ['account_banking'],
|
|
||||||
'description': '''
|
|
||||||
Module to import Dutch Triodos bank format transation files (CSV format).
|
|
||||||
|
|
||||||
As the Triodos bank does not provide detailed specification concerning possible
|
|
||||||
values and their meaning for the fields in the CSV file format, the statements
|
|
||||||
are parsed according to an educated guess based on incomplete information.
|
|
||||||
You can contact the account-banking developers through their launchpad page and
|
|
||||||
help improve the performance of this import filter on
|
|
||||||
https://launchpad.net/account-banking.
|
|
||||||
|
|
||||||
Note that imported bank transfers are organized in statements covering periods
|
|
||||||
of one week, even if the imported files cover a different period.
|
|
||||||
|
|
||||||
This modules contains no logic, just an import filter for account_banking.
|
|
||||||
''',
|
|
||||||
'installable': False,
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# Translation of OpenERP Server.
|
|
||||||
# This file contains the translation of the following modules:
|
|
||||||
# * account_banking_nl_triodos
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: OpenERP Server 7.0\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2013-10-25 15:57+0000\n"
|
|
||||||
"PO-Revision-Date: 2013-10-25 15:57+0000\n"
|
|
||||||
"Last-Translator: <>\n"
|
|
||||||
"Language-Team: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: \n"
|
|
||||||
"Plural-Forms: \n"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_triodos
|
|
||||||
#: code:addons/account_banking_nl_triodos/triodos.py:183
|
|
||||||
#, python-format
|
|
||||||
msgid "Triodos Bank"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_triodos
|
|
||||||
#: code:addons/account_banking_nl_triodos/triodos.py:184
|
|
||||||
#, python-format
|
|
||||||
msgid "The Dutch Triodos format is basicly a MS Excel CSV format. It is specifically\n"
|
|
||||||
"distinct from the Dutch multibank format. Transactions are not tied to Bank\n"
|
|
||||||
"Statements.\n"
|
|
||||||
""
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_triodos
|
|
||||||
#: code:addons/account_banking_nl_triodos/triodos.py:59
|
|
||||||
#, python-format
|
|
||||||
msgid "Invalid transaction line: expected %d columns, found %d"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
# Translation of OpenERP Server.
|
|
||||||
# This file contains the translation of the following modules:
|
|
||||||
# * account_banking_nl_triodos
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: OpenERP Server 6.0.1\n"
|
|
||||||
"Report-Msgid-Bugs-To: support@openerp.com\n"
|
|
||||||
"POT-Creation-Date: 2013-10-25 15:57+0000\n"
|
|
||||||
"PO-Revision-Date: 2013-11-11 17:51+0000\n"
|
|
||||||
"Last-Translator: Pedro Manuel Baeza <pedro.baeza@gmail.com>\n"
|
|
||||||
"Language-Team: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"X-Launchpad-Export-Date: 2014-05-31 06:02+0000\n"
|
|
||||||
"X-Generator: Launchpad (build 17031)\n"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_triodos
|
|
||||||
#: code:addons/account_banking_nl_triodos/triodos.py:183
|
|
||||||
#, python-format
|
|
||||||
msgid "Triodos Bank"
|
|
||||||
msgstr "Triodos Bank"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_triodos
|
|
||||||
#: code:addons/account_banking_nl_triodos/triodos.py:184
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"The Dutch Triodos format is basicly a MS Excel CSV format. It is "
|
|
||||||
"specifically\n"
|
|
||||||
"distinct from the Dutch multibank format. Transactions are not tied to Bank\n"
|
|
||||||
"Statements.\n"
|
|
||||||
msgstr ""
|
|
||||||
"The Dutch Triodos format is basicly a MS Excel CSV format. It is "
|
|
||||||
"specifically\n"
|
|
||||||
"distinct from the Dutch multibank format. Transactions are not tied to Bank\n"
|
|
||||||
"Statements.\n"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_triodos
|
|
||||||
#: code:addons/account_banking_nl_triodos/triodos.py:59
|
|
||||||
#, python-format
|
|
||||||
msgid "Invalid transaction line: expected %d columns, found %d"
|
|
||||||
msgstr "Invalid transaction line: expected %d columns, found %d"
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
# Translation of OpenERP Server.
|
|
||||||
# This file contains the translation of the following modules:
|
|
||||||
# * account_banking_nl_triodos
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: OpenERP Server 6.0.1\n"
|
|
||||||
"Report-Msgid-Bugs-To: support@openerp.com\n"
|
|
||||||
"POT-Creation-Date: 2013-10-25 15:57+0000\n"
|
|
||||||
"PO-Revision-Date: 2013-12-03 11:15+0000\n"
|
|
||||||
"Last-Translator: Jan Jurkus (GCE CAD-Service) <ict@gcecad-service.nl>\n"
|
|
||||||
"Language-Team: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"X-Launchpad-Export-Date: 2014-05-31 06:02+0000\n"
|
|
||||||
"X-Generator: Launchpad (build 17031)\n"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_triodos
|
|
||||||
#: code:addons/account_banking_nl_triodos/triodos.py:183
|
|
||||||
#, python-format
|
|
||||||
msgid "Triodos Bank"
|
|
||||||
msgstr "Triodosbank"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_triodos
|
|
||||||
#: code:addons/account_banking_nl_triodos/triodos.py:184
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"The Dutch Triodos format is basicly a MS Excel CSV format. It is "
|
|
||||||
"specifically\n"
|
|
||||||
"distinct from the Dutch multibank format. Transactions are not tied to Bank\n"
|
|
||||||
"Statements.\n"
|
|
||||||
msgstr ""
|
|
||||||
"Het Nederlandse Triodos-formaat is in opzet een MS Excel CSV-formaat. Het "
|
|
||||||
"is\n"
|
|
||||||
"duidelijk afwijkend van het Nederlandse multibank-formaat. Transacties zijn\n"
|
|
||||||
"niet gebonden aan bankafschriften.\n"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_triodos
|
|
||||||
#: code:addons/account_banking_nl_triodos/triodos.py:59
|
|
||||||
#, python-format
|
|
||||||
msgid "Invalid transaction line: expected %d columns, found %d"
|
|
||||||
msgstr "Ongeldige transactieregel: %d kolommen verwacht, %d aangetroffen"
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
# Brazilian Portuguese translation for banking-addons
|
|
||||||
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
|
|
||||||
# This file is distributed under the same license as the banking-addons package.
|
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: banking-addons\n"
|
|
||||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
|
||||||
"POT-Creation-Date: 2013-10-25 15:57+0000\n"
|
|
||||||
"PO-Revision-Date: 2013-12-25 12:29+0000\n"
|
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
||||||
"Language-Team: Brazilian Portuguese <pt_BR@li.org>\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"X-Launchpad-Export-Date: 2014-05-31 06:02+0000\n"
|
|
||||||
"X-Generator: Launchpad (build 17031)\n"
|
|
||||||
|
|
||||||
#. module: account_banking_nl_triodos
|
|
||||||
#: code:addons/account_banking_nl_triodos/triodos.py:183
|
|
||||||
#, python-format
|
|
||||||
msgid "Triodos Bank"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_triodos
|
|
||||||
#: code:addons/account_banking_nl_triodos/triodos.py:184
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"The Dutch Triodos format is basicly a MS Excel CSV format. It is "
|
|
||||||
"specifically\n"
|
|
||||||
"distinct from the Dutch multibank format. Transactions are not tied to Bank\n"
|
|
||||||
"Statements.\n"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_nl_triodos
|
|
||||||
#: code:addons/account_banking_nl_triodos/triodos.py:59
|
|
||||||
#, python-format
|
|
||||||
msgid "Invalid transaction line: expected %d columns, found %d"
|
|
||||||
msgstr ""
|
|
||||||
@@ -1,229 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>),
|
|
||||||
# 2011 Therp BV (<http://therp.nl>).
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
'''
|
|
||||||
This parser follows the Dutch Banking Tools specifications which are
|
|
||||||
empirically recreated in this module.
|
|
||||||
|
|
||||||
Dutch Banking Tools uses the concept of 'Afschrift' or Bank Statement.
|
|
||||||
Every transaction is bound to a Bank Statement. As such, this module generates
|
|
||||||
Bank Statements along with Bank Transactions.
|
|
||||||
'''
|
|
||||||
|
|
||||||
from account_banking.parsers import models
|
|
||||||
from account_banking.parsers.convert import str2date
|
|
||||||
from tools.translate import _
|
|
||||||
|
|
||||||
import re
|
|
||||||
import csv
|
|
||||||
|
|
||||||
__all__ = ['parser']
|
|
||||||
|
|
||||||
bt = models.mem_bank_transaction
|
|
||||||
|
|
||||||
|
|
||||||
class transaction_message(object):
|
|
||||||
'''
|
|
||||||
A auxiliary class to validate and coerce read values
|
|
||||||
'''
|
|
||||||
attrnames = [
|
|
||||||
'date', 'local_account', 'transferred_amount', 'debcred',
|
|
||||||
'remote_owner', 'remote_account', 'transfer_type', 'reference',
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, values, subno):
|
|
||||||
'''
|
|
||||||
Initialize own dict with attributes and coerce values to right type
|
|
||||||
'''
|
|
||||||
if len(self.attrnames) != len(values):
|
|
||||||
raise ValueError(
|
|
||||||
_('Invalid transaction line: expected %d columns, found %d')
|
|
||||||
% (len(self.attrnames), len(values)))
|
|
||||||
self.__dict__.update(dict(zip(self.attrnames, values)))
|
|
||||||
# for lack of a standardized locale function to parse amounts
|
|
||||||
self.transferred_amount = float(
|
|
||||||
re.sub(',', '.', re.sub('\.', '', self.transferred_amount)))
|
|
||||||
if self.debcred == 'Debet':
|
|
||||||
self.transferred_amount = -self.transferred_amount
|
|
||||||
self.execution_date = str2date(self.date, '%d-%m-%Y')
|
|
||||||
self.value_date = str2date(self.date, '%d-%m-%Y')
|
|
||||||
self.statement_id = ''
|
|
||||||
self.id = str(subno).zfill(4)
|
|
||||||
# Normalize basic account numbers
|
|
||||||
self.remote_account = self.remote_account.replace('.', '').zfill(10)
|
|
||||||
self.local_account = self.local_account.replace('.', '').zfill(10)
|
|
||||||
|
|
||||||
|
|
||||||
class transaction(models.mem_bank_transaction):
|
|
||||||
'''
|
|
||||||
Implementation of transaction communication class for account_banking.
|
|
||||||
'''
|
|
||||||
attrnames = [
|
|
||||||
'local_account',
|
|
||||||
'remote_account',
|
|
||||||
'remote_owner',
|
|
||||||
'transferred_amount',
|
|
||||||
'execution_date',
|
|
||||||
'value_date',
|
|
||||||
'transfer_type',
|
|
||||||
'reference',
|
|
||||||
'id',
|
|
||||||
]
|
|
||||||
|
|
||||||
type_map = {
|
|
||||||
# retrieved from online help in the Triodos banking application
|
|
||||||
'AC': bt.ORDER, # Acceptgiro gecodeerd
|
|
||||||
'AN': bt.ORDER, # Acceptgiro ongecodeerd
|
|
||||||
'AT': bt.ORDER, # Acceptgiro via internet
|
|
||||||
'BA': bt.PAYMENT_TERMINAL, # Betaalautomaat
|
|
||||||
'CHIP': bt.BANK_TERMINAL, # Chipknip
|
|
||||||
# 'CO': # Correctie
|
|
||||||
'DB': bt.ORDER, # Diskettebetaling
|
|
||||||
# 'DV': # Dividend
|
|
||||||
'EI': bt.DIRECT_DEBIT, # Europese Incasso
|
|
||||||
'EICO': bt.DIRECT_DEBIT, # Europese Incasso Correctie
|
|
||||||
'EIST': bt.ORDER, # Europese Incasso Storno
|
|
||||||
'ET': bt.ORDER, # Europese Transactie
|
|
||||||
'ETST': bt.ORDER, # Europese Transactie Storno
|
|
||||||
'GA': bt.BANK_TERMINAL, # Geldautomaat
|
|
||||||
'IB': bt.ORDER, # Interne Boeking
|
|
||||||
'IC': bt.DIRECT_DEBIT, # Incasso
|
|
||||||
'ID': bt.ORDER, # iDeal-betaling
|
|
||||||
'IT': bt.ORDER, # Internet transactie
|
|
||||||
'KN': bt.BANK_COSTS, # Kosten
|
|
||||||
'KO': bt.BANK_TERMINAL, # Kasopname
|
|
||||||
# 'KS': # Kwaliteitsstoring
|
|
||||||
'OV': bt.ORDER, # Overboeking. NB: can also be bt.BANK_COSTS
|
|
||||||
# when no remote_account specified!
|
|
||||||
'PO': bt.ORDER, # Periodieke Overboeking
|
|
||||||
'PR': bt.BANK_COSTS, # Provisie
|
|
||||||
# 'RE': # Rente
|
|
||||||
# 'RS': # Renteschenking
|
|
||||||
'ST': bt.ORDER, # Storno
|
|
||||||
'TG': bt.ORDER, # Telegiro
|
|
||||||
# 'VL': # Vaste Lening
|
|
||||||
'VO': bt.DIRECT_DEBIT, # Vordering overheid
|
|
||||||
'VV': bt.ORDER, # Vreemde valuta
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, line, *args, **kwargs):
|
|
||||||
'''
|
|
||||||
Initialize own dict with read values.
|
|
||||||
'''
|
|
||||||
super(transaction, self).__init__(*args, **kwargs)
|
|
||||||
# Copy attributes from auxiliary class to self.
|
|
||||||
for attr in self.attrnames:
|
|
||||||
setattr(self, attr, getattr(line, attr))
|
|
||||||
self.message = ''
|
|
||||||
# Decompose structured messages
|
|
||||||
self.parse_message()
|
|
||||||
if (self.transfer_type == 'OV'
|
|
||||||
and not self.remote_account
|
|
||||||
and not self.remote_owner):
|
|
||||||
self.transfer_type = 'KN'
|
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
if not self.error_message:
|
|
||||||
if not self.transferred_amount:
|
|
||||||
self.error_message = "No transferred amount"
|
|
||||||
elif not self.execution_date:
|
|
||||||
self.error_message = "No execution date"
|
|
||||||
elif not self.remote_account and self.transfer_type not in [
|
|
||||||
'KN', 'TG', 'GA', 'BA', 'CHIP']:
|
|
||||||
self.error_message = (
|
|
||||||
"No remote account for transaction type %s" %
|
|
||||||
self.transfer_type)
|
|
||||||
return not self.error_message
|
|
||||||
|
|
||||||
def parse_message(self):
|
|
||||||
'''
|
|
||||||
Parse structured message parts into appropriate attributes.
|
|
||||||
'''
|
|
||||||
# IBAN accounts are prefixed by BIC
|
|
||||||
if self.remote_account:
|
|
||||||
parts = self.remote_account.split(' ')
|
|
||||||
if len(parts) == 2:
|
|
||||||
self.remote_bank_bic = parts[0]
|
|
||||||
self.remote_account = parts[1]
|
|
||||||
|
|
||||||
|
|
||||||
class statement(models.mem_bank_statement):
|
|
||||||
'''
|
|
||||||
Implementation of bank_statement communication class of account_banking
|
|
||||||
'''
|
|
||||||
def __init__(self, msg, *args, **kwargs):
|
|
||||||
'''
|
|
||||||
Set decent start values based on first transaction read
|
|
||||||
'''
|
|
||||||
super(statement, self).__init__(*args, **kwargs)
|
|
||||||
self.id = msg.statement_id
|
|
||||||
self.local_account = msg.local_account
|
|
||||||
self.date = str2date(msg.date, '%d-%m-%Y')
|
|
||||||
self.start_balance = self.end_balance = 0 # msg.start_balance
|
|
||||||
self.import_transaction(msg)
|
|
||||||
|
|
||||||
def import_transaction(self, msg):
|
|
||||||
'''
|
|
||||||
Import a transaction and keep some house holding in the mean time.
|
|
||||||
'''
|
|
||||||
trans = transaction(msg)
|
|
||||||
self.end_balance += trans.transferred_amount
|
|
||||||
self.transactions.append(trans)
|
|
||||||
|
|
||||||
|
|
||||||
class parser(models.parser):
|
|
||||||
code = 'TRIOD'
|
|
||||||
country_code = 'NL'
|
|
||||||
name = _('Triodos Bank')
|
|
||||||
doc = _('''\
|
|
||||||
The Dutch Triodos format is basicly a MS Excel CSV format. It is specifically
|
|
||||||
distinct from the Dutch multibank format. Transactions are not tied to Bank
|
|
||||||
Statements.
|
|
||||||
''')
|
|
||||||
|
|
||||||
def parse(self, cr, data):
|
|
||||||
result = []
|
|
||||||
stmnt = None
|
|
||||||
dialect = csv.excel()
|
|
||||||
dialect.quotechar = '"'
|
|
||||||
dialect.delimiter = ','
|
|
||||||
lines = data.split('\n')
|
|
||||||
# Transaction lines are not numbered, so keep a tracer
|
|
||||||
subno = 0
|
|
||||||
# fixed statement id based on import timestamp
|
|
||||||
statement_id = False
|
|
||||||
for line in csv.reader(lines, dialect=dialect):
|
|
||||||
# Skip empty (last) lines
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
subno += 1
|
|
||||||
msg = transaction_message(line, subno)
|
|
||||||
if not statement_id:
|
|
||||||
statement_id = self.get_unique_statement_id(
|
|
||||||
cr, msg.execution_date.strftime('%Yw%W'))
|
|
||||||
msg.statement_id = statement_id
|
|
||||||
if stmnt:
|
|
||||||
stmnt.import_transaction(msg)
|
|
||||||
else:
|
|
||||||
stmnt = statement(msg)
|
|
||||||
result.append(stmnt)
|
|
||||||
return result
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
# -*- 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 account_banking_uk_hsbc
|
|
||||||
import wizard
|
|
||||||
import hsbc_mt940
|
|
||||||
import hsbc_clientid
|
|
||||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
# -*- 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
{
|
|
||||||
'name': 'HSBC Account Banking',
|
|
||||||
'version': '0.5',
|
|
||||||
'license': 'AGPL-3',
|
|
||||||
'author': 'credativ Ltd',
|
|
||||||
'website': 'http://www.credativ.co.uk',
|
|
||||||
'category': 'Account Banking',
|
|
||||||
'depends': ['account_banking_payment'],
|
|
||||||
'data': [
|
|
||||||
'account_banking_uk_hsbc.xml',
|
|
||||||
'hsbc_clientid_view.xml',
|
|
||||||
'data/banking_export_hsbc.xml',
|
|
||||||
'wizard/export_hsbc_view.xml',
|
|
||||||
'security/ir.model.access.csv',
|
|
||||||
],
|
|
||||||
'description': '''
|
|
||||||
Module to import HSBC format transation files (S.W.I.F.T MT940) and to export
|
|
||||||
payments for HSBC.net (PAYMUL).
|
|
||||||
|
|
||||||
Currently it is targetting UK market, due to country variances of the MT940 and
|
|
||||||
PAYMUL.
|
|
||||||
|
|
||||||
It is possible to extend this module to work with HSBC.net in other countries
|
|
||||||
and potentially other banks.
|
|
||||||
|
|
||||||
This module adds above import/export filter to the account_banking module.
|
|
||||||
All business logic is in account_banking module.
|
|
||||||
|
|
||||||
Initial release of this module was co-sponsored by Canonical.
|
|
||||||
''',
|
|
||||||
'installable': False,
|
|
||||||
}
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
from datetime import date
|
|
||||||
|
|
||||||
from openerp.osv import orm, fields
|
|
||||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as OE_DATEFORMAT
|
|
||||||
|
|
||||||
|
|
||||||
class hsbc_export(orm.Model):
|
|
||||||
"""HSBC Export"""
|
|
||||||
_name = 'banking.export.hsbc'
|
|
||||||
_description = __doc__
|
|
||||||
_rec_name = 'execution_date'
|
|
||||||
|
|
||||||
_columns = {
|
|
||||||
'payment_order_ids': fields.many2many(
|
|
||||||
'payment.order',
|
|
||||||
'account_payment_order_hsbc_rel',
|
|
||||||
'banking_export_hsbc_id', 'account_order_id',
|
|
||||||
'Payment Orders',
|
|
||||||
readonly=True),
|
|
||||||
'identification':
|
|
||||||
fields.char('Identification', size=15, readonly=True, select=True),
|
|
||||||
'execution_date':
|
|
||||||
fields.date('Execution Date', readonly=True),
|
|
||||||
'no_transactions':
|
|
||||||
fields.integer('Number of Transactions', readonly=True),
|
|
||||||
'total_amount':
|
|
||||||
fields.float('Total Amount', readonly=True),
|
|
||||||
'date_generated':
|
|
||||||
fields.datetime('Generation Date', readonly=True, select=True),
|
|
||||||
'file':
|
|
||||||
fields.binary('HSBC File', readonly=True),
|
|
||||||
'state':
|
|
||||||
fields.selection([
|
|
||||||
('draft', 'Draft'),
|
|
||||||
('sent', 'Sent'),
|
|
||||||
('done', 'Reconciled'),
|
|
||||||
], 'State', readonly=True),
|
|
||||||
}
|
|
||||||
|
|
||||||
_defaults = {
|
|
||||||
'date_generated': lambda *a: date.today().strftime(OE_DATEFORMAT),
|
|
||||||
'state': 'draft',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class payment_line(orm.Model):
|
|
||||||
"""The standard payment order is using a mixture of details from the
|
|
||||||
partner record and the res.partner.bank record. For, instance, the account
|
|
||||||
holder name is coming from the res.partner.bank record, but the company
|
|
||||||
name and address are coming from the partner address record. This is
|
|
||||||
problematic because the HSBC payment format is validating for alphanumeric
|
|
||||||
characters in the company name and address. So, "Great Company Ltd." and
|
|
||||||
"Great Company s.a." will cause an error because they have full-stops in
|
|
||||||
the name.
|
|
||||||
|
|
||||||
A better approach is to use the name and address details from the
|
|
||||||
res.partner.bank record always. This way, the address details can be
|
|
||||||
sanitized for the payments, whilst being able to print the proper name and
|
|
||||||
address throughout the rest of the system e.g. on invoices.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_inherit = 'payment.line'
|
|
||||||
|
|
||||||
def info_owner(self, cr, uid, ids, name=None, args=None, context=None):
|
|
||||||
|
|
||||||
if not ids:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
result = {}
|
|
||||||
info = ''
|
|
||||||
for line in self.browse(cr, uid, ids, context=context):
|
|
||||||
owner = line.order_id.mode.bank_id
|
|
||||||
|
|
||||||
name = owner.owner_name or owner.partner_id.name
|
|
||||||
st = owner.street and owner.street or ''
|
|
||||||
st1 = '' # no street2 in res.partner.bank
|
|
||||||
zip = owner.zip and owner.zip or ''
|
|
||||||
city = owner.city and owner.city or ''
|
|
||||||
zip_city = zip + ' ' + city
|
|
||||||
cntry = owner.country_id and owner.country_id.name or ''
|
|
||||||
info = name + "\n".join((st + " ", st1, zip_city, cntry))
|
|
||||||
result[line.id] = info
|
|
||||||
return result
|
|
||||||
|
|
||||||
def info_partner(self, cr, uid, ids, name=None, args=None, context=None):
|
|
||||||
if not ids:
|
|
||||||
return {}
|
|
||||||
result = {}
|
|
||||||
info = ''
|
|
||||||
|
|
||||||
for line in self.browse(cr, uid, ids, context=context):
|
|
||||||
partner = line.bank_id
|
|
||||||
|
|
||||||
name = partner.owner_name or partner.partner_id.name
|
|
||||||
st = partner.street and partner.street or ''
|
|
||||||
st1 = '' # no street2 in res.partner.bank
|
|
||||||
zip = partner.zip and partner.zip or ''
|
|
||||||
city = partner.city and partner.city or ''
|
|
||||||
zip_city = zip + ' ' + city
|
|
||||||
cntry = partner.country_id and partner.country_id.name or ''
|
|
||||||
info = name + "\n".join((st + " ", st1, zip_city, cntry))
|
|
||||||
result[line.id] = info
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
# Define the info_partner and info_owner so we can override the methods
|
|
||||||
_columns = {
|
|
||||||
'info_owner': fields.function(
|
|
||||||
info_owner,
|
|
||||||
string="Owner Account",
|
|
||||||
type="text",
|
|
||||||
help='Address of the Main Partner',
|
|
||||||
),
|
|
||||||
'info_partner': fields.function(
|
|
||||||
info_partner,
|
|
||||||
string="Destination Account",
|
|
||||||
type="text",
|
|
||||||
help='Address of the Ordering Customer.'
|
|
||||||
),
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
Copyright (C) EduSense BV <http://www.edusense.nl>
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Copyright (C) 2011 credativ Ltd (<http://www.credativ.co.uk>)
|
|
||||||
|
|
||||||
The licence is in the file __openerp__.py
|
|
||||||
|
|
||||||
|
|
||||||
-->
|
|
||||||
<openerp>
|
|
||||||
<data>
|
|
||||||
|
|
||||||
<!-- Make new view on HSBC exports -->
|
|
||||||
<record id="view_banking_export_hsbc_form" model="ir.ui.view">
|
|
||||||
<field name="name">account.banking.export.hsbc.form</field>
|
|
||||||
<field name="model">banking.export.hsbc</field>
|
|
||||||
<field name="type">form</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<form string="HSBC Export">
|
|
||||||
<notebook>
|
|
||||||
<page string="General Information">
|
|
||||||
<separator string="HSBC Information" colspan="4" />
|
|
||||||
<field name="total_amount" />
|
|
||||||
<field name="no_transactions" />
|
|
||||||
<separator string="Processing Information" colspan="4" />
|
|
||||||
<field name="execution_date" />
|
|
||||||
<field name="date_generated" />
|
|
||||||
<newline />
|
|
||||||
<field name="file" colspan="4" />
|
|
||||||
</page>
|
|
||||||
<page string="Payment Orders">
|
|
||||||
<field name="payment_order_ids" colspan="4" nolabel="1">
|
|
||||||
<tree colors="blue:state in ('draft');gray:state in ('cancel','done');black:state in ('open')" string="Payment order">
|
|
||||||
<field name="reference"/>
|
|
||||||
<field name="date_created"/>
|
|
||||||
<field name="date_done"/>
|
|
||||||
<field name="total"/>
|
|
||||||
<field name="state"/>
|
|
||||||
</tree>
|
|
||||||
</field>
|
|
||||||
</page>
|
|
||||||
</notebook>
|
|
||||||
</form>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
<record id="view_banking_export_hsbc_tree" model="ir.ui.view">
|
|
||||||
<field name="name">account.banking.export.hsbc.tree</field>
|
|
||||||
<field name="model">banking.export.hsbc</field>
|
|
||||||
<field name="type">tree</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<tree string="HSBC Export">
|
|
||||||
<field name="execution_date" search="2"/>
|
|
||||||
<field name="date_generated" />
|
|
||||||
</tree>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
<record model="ir.actions.act_window" id="action_account_banking_hsbcs">
|
|
||||||
<field name="name">Generated HSBC files</field>
|
|
||||||
<field name="type">ir.actions.act_window</field>
|
|
||||||
<field name="res_model">banking.export.hsbc</field>
|
|
||||||
<field name="view_type">form</field>
|
|
||||||
<field name="view_mode">tree,form</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<!-- Add a menu item for it -->
|
|
||||||
<menuitem name="Generated HSBC files"
|
|
||||||
id="menu_action_account_banking_exported_hsbc_files"
|
|
||||||
parent="account_banking.menu_finance_banking_actions"
|
|
||||||
action="action_account_banking_hsbcs"
|
|
||||||
sequence="12"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Create right menu entry to see generated files -->
|
|
||||||
<act_window name="Generated HSBC files"
|
|
||||||
domain="[('payment_order_ids', '=', active_id)]"
|
|
||||||
res_model="banking.export.hsbc"
|
|
||||||
src_model="payment.order"
|
|
||||||
view_type="form"
|
|
||||||
view_mode="tree,form"
|
|
||||||
id="act_banking_export_hsbc_payment_order"/>
|
|
||||||
|
|
||||||
</data>
|
|
||||||
</openerp>
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<openerp>
|
|
||||||
<data>
|
|
||||||
<record model="payment.mode.type" id="export_acm_or_ezone">
|
|
||||||
<field name="name">ACH or EZONE</field>
|
|
||||||
<field name="code">not used</field>
|
|
||||||
<field name="suitable_bank_types"
|
|
||||||
eval="[(6,0,[ref('base_iban.bank_iban'),ref('base.bank_normal'),])]" />
|
|
||||||
<field name="ir_model_id"
|
|
||||||
ref="account_banking_uk_hsbc.model_banking_export_hsbc_wizard"/>
|
|
||||||
</record>
|
|
||||||
<record model="payment.mode.type" id="export_faster_payment">
|
|
||||||
<field name="name">Faster Payment</field>
|
|
||||||
<field name="code">not used</field>
|
|
||||||
<field name="suitable_bank_types"
|
|
||||||
eval="[(6,0,[ref('base.bank_normal'),])]" />
|
|
||||||
<field name="ir_model_id"
|
|
||||||
ref="account_banking_uk_hsbc.model_banking_export_hsbc_wizard"/>
|
|
||||||
</record>
|
|
||||||
<record model="payment.mode.type" id="export_priority_payment">
|
|
||||||
<field name="name">Priority Payment</field>
|
|
||||||
<field name="code">not used</field>
|
|
||||||
<field name="suitable_bank_types"
|
|
||||||
eval="[(6,0,[ref('base_iban.bank_iban'),ref('base.bank_normal'),])]" />
|
|
||||||
<field name="ir_model_id"
|
|
||||||
ref="account_banking_uk_hsbc.model_banking_export_hsbc_wizard"/>
|
|
||||||
</record>
|
|
||||||
</data>
|
|
||||||
</openerp>
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
from openerp.osv import orm, fields
|
|
||||||
|
|
||||||
|
|
||||||
class hsbc_clientid(orm.Model):
|
|
||||||
"""
|
|
||||||
Record to hold the HSBCNet Client ID for the company.
|
|
||||||
"""
|
|
||||||
_name = 'banking.hsbc.clientid'
|
|
||||||
_description = 'HSBC Client ID'
|
|
||||||
|
|
||||||
_columns = {
|
|
||||||
'name': fields.char('Name', size=64, required=True),
|
|
||||||
'clientid': fields.char('Client ID', size=20, required=True),
|
|
||||||
'company_id': fields.many2one('res.company', 'Company', required=True),
|
|
||||||
}
|
|
||||||
|
|
||||||
_defaults = {
|
|
||||||
'company_id': (
|
|
||||||
lambda self, cr, uid, c:
|
|
||||||
self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class payment_order(orm.Model):
|
|
||||||
_inherit = 'payment.order'
|
|
||||||
_columns = {
|
|
||||||
'hsbc_clientid_id': fields.many2one(
|
|
||||||
'banking.hsbc.clientid',
|
|
||||||
'HSBC Client ID',
|
|
||||||
required=True,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
def _default_hsbc_clientid(self, cr, uid, context=None):
|
|
||||||
user = self.pool['res.users'].browse(cr, uid, uid, context=context)
|
|
||||||
company_id = user.company_id.id
|
|
||||||
|
|
||||||
clientid_ids = self.pool['banking.hsbc.clientid'].search(
|
|
||||||
cr, uid, [('company_id', '=', company_id)]
|
|
||||||
)
|
|
||||||
if len(clientid_ids) == 0:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return clientid_ids[0]
|
|
||||||
|
|
||||||
_defaults = {
|
|
||||||
'hsbc_clientid_id': _default_hsbc_clientid,
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<openerp>
|
|
||||||
<data>
|
|
||||||
|
|
||||||
<!-- Add the HSBC Client ID to the Payment Order -->
|
|
||||||
<record id="view_payment_order_form" model="ir.ui.view">
|
|
||||||
<field name="name">payment.order.form</field>
|
|
||||||
<field name="model">payment.order</field>
|
|
||||||
<field name="type">form</field>
|
|
||||||
<field name="inherit_id" ref="account_payment.view_payment_order_form"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<field name="date_scheduled" position="after">
|
|
||||||
<field name="hsbc_clientid_id" />
|
|
||||||
</field>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Form view for HSBC Client ID -->
|
|
||||||
<record id="banking_hsbc_clientid_form" model="ir.ui.view">
|
|
||||||
<field name="name">banking.hsbc.clientid.form</field>
|
|
||||||
<field name="model">banking.hsbc.clientid</field>
|
|
||||||
<field name="type">form</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<form string="HSBC Client ID">
|
|
||||||
<group colspan="4">
|
|
||||||
<field name="name" />
|
|
||||||
<field name="company_id" />
|
|
||||||
<field name="clientid" />
|
|
||||||
</group>
|
|
||||||
</form>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<!-- Tree view for HSBC Client ID -->
|
|
||||||
<record id="banking_hsbc_clientid_tree" model="ir.ui.view">
|
|
||||||
<field name="name">banking.hsbc.clientid.tree</field>
|
|
||||||
<field name="model">banking.hsbc.clientid</field>
|
|
||||||
<field name="type">tree</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<tree string="HSBC Client IDs">
|
|
||||||
<field name="name" />
|
|
||||||
<field name="company_id" />
|
|
||||||
<field name="clientid" />
|
|
||||||
</tree>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<!-- Search view for HSBC Client ID -->
|
|
||||||
<record id="banking_hsbc_clientid_filter" model="ir.ui.view">
|
|
||||||
<field name="name">banking.hsbc.clientid.filter</field>
|
|
||||||
<field name="model">banking.hsbc.clientid</field>
|
|
||||||
<field name="type">search</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<search string="HSBC Client IDs">
|
|
||||||
<field name="name"/>
|
|
||||||
<field name="company_id" />
|
|
||||||
<field name="clientid" />
|
|
||||||
</search>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<!-- Action for HSBC Client ID -->
|
|
||||||
<record id="banking_hsbc_clientid_action" model="ir.actions.act_window">
|
|
||||||
<field name="name">HSBC Client ID</field>
|
|
||||||
<field name="res_model">banking.hsbc.clientid</field>
|
|
||||||
<field name="view_type">form</field>
|
|
||||||
<field name="view_mode">tree,form</field>
|
|
||||||
<field name="search_view_id" ref="banking_hsbc_clientid_filter"/>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<!-- Menu for HSBC Client ID -->
|
|
||||||
<menuitem action="banking_hsbc_clientid_action" id="banking_hsbc_clientid_menu" parent="account.menu_configuration_misc"/>
|
|
||||||
|
|
||||||
</data>
|
|
||||||
</openerp>
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
# -*- 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 of HSBC data in Swift MT940 format
|
|
||||||
#
|
|
||||||
|
|
||||||
from account_banking.parsers import models
|
|
||||||
from mt940_parser import HSBCParser
|
|
||||||
import re
|
|
||||||
import logging
|
|
||||||
|
|
||||||
bt = models.mem_bank_transaction
|
|
||||||
logger = logging.getLogger('hsbc_mt940')
|
|
||||||
|
|
||||||
from openerp.tools.translate import _
|
|
||||||
from openerp.osv import orm
|
|
||||||
|
|
||||||
|
|
||||||
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',
|
|
||||||
'value_date': 'valuedate',
|
|
||||||
'local_currency': 'currency',
|
|
||||||
'transfer_type': 'bookingcode',
|
|
||||||
'reference': 'custrefno',
|
|
||||||
'message': 'furtherinfo'
|
|
||||||
}
|
|
||||||
|
|
||||||
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')
|
|
||||||
|
|
||||||
# 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 import_record(self, record):
|
|
||||||
def _transmission_number():
|
|
||||||
self.id = record['transref']
|
|
||||||
|
|
||||||
def _account_number():
|
|
||||||
# The wizard doesn't check for sort code
|
|
||||||
self.local_account = (
|
|
||||||
record['sortcode'] + ' ' + record['accnum'].zfill(8)
|
|
||||||
)
|
|
||||||
|
|
||||||
def _statement_number():
|
|
||||||
self.id = '-'.join(
|
|
||||||
[self.id, self.local_account, record['statementnr']]
|
|
||||||
)
|
|
||||||
|
|
||||||
def _opening_balance():
|
|
||||||
self.start_balance = record2float(record, 'startingbalance')
|
|
||||||
self.local_currency = record['currencycode']
|
|
||||||
|
|
||||||
def _closing_balance():
|
|
||||||
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,
|
|
||||||
'61': _transaction_new,
|
|
||||||
'86': _transaction_info,
|
|
||||||
}
|
|
||||||
|
|
||||||
rectypes.get(record['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]
|
|
||||||
transaction.id = ','.join((
|
|
||||||
record[k]
|
|
||||||
for k in (
|
|
||||||
'infoline{0}'.format(i)
|
|
||||||
for i in range(2, 5)
|
|
||||||
)
|
|
||||||
if k in record
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
def raise_error(message, line):
|
|
||||||
raise orm.except_orm(
|
|
||||||
_('Import error'),
|
|
||||||
_('Error in import:') + '\n\n'.join((message, line))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class parser_hsbc_mt940(models.parser):
|
|
||||||
code = 'HSBC-MT940'
|
|
||||||
name = _('HSBC Swift MT940 statement export')
|
|
||||||
country_code = 'GB'
|
|
||||||
doc = _('''\
|
|
||||||
This format is available through
|
|
||||||
the HSBC web interface.
|
|
||||||
''')
|
|
||||||
|
|
||||||
def parse(self, cr, data):
|
|
||||||
result = []
|
|
||||||
parser = HSBCParser()
|
|
||||||
# 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
|
|
||||||
]
|
|
||||||
|
|
||||||
for statement_lines in statement_list:
|
|
||||||
stmnt = statement()
|
|
||||||
records = [
|
|
||||||
parser.parse_record(record)
|
|
||||||
for record in statement_lines
|
|
||||||
]
|
|
||||||
[stmnt.import_record(r) for r in records if r is not None]
|
|
||||||
|
|
||||||
if stmnt.is_valid():
|
|
||||||
result.append(stmnt)
|
|
||||||
else:
|
|
||||||
logger.info("Invalid Statement:")
|
|
||||||
logger.info(records[0])
|
|
||||||
|
|
||||||
return result
|
|
||||||
@@ -1,287 +0,0 @@
|
|||||||
# Translation of OpenERP Server.
|
|
||||||
# This file contains the translation of the following modules:
|
|
||||||
# * account_banking_uk_hsbc
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: OpenERP Server 7.0\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2013-10-25 15:59+0000\n"
|
|
||||||
"PO-Revision-Date: 2013-10-25 15:59+0000\n"
|
|
||||||
"Last-Translator: <>\n"
|
|
||||||
"Language-Team: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: \n"
|
|
||||||
"Plural-Forms: \n"
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: help:banking.export.hsbc.wizard,execution_date_create:0
|
|
||||||
msgid "This is the date the file should be processed by the bank. Don't choose a date beyond the nearest date in your payments. The latest allowed date is 30 days from now.\n"
|
|
||||||
"Please keep in mind that banks only execute on working days and typically use a delay of two days between execution date and effective transfer date."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: view:banking.export.hsbc:0
|
|
||||||
msgid "HSBC Information"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: code:addons/account_banking_uk_hsbc/wizard/export_hsbc.py:234
|
|
||||||
#, python-format
|
|
||||||
msgid "There is insufficient information.
|
|
||||||
\n"
|
|
||||||
"Both destination address and account number must be provided"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: view:banking.hsbc.clientid:0
|
|
||||||
msgid "HSBC Client IDs"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: code:addons/account_banking_uk_hsbc/hsbc_mt940.py:143
|
|
||||||
#, python-format
|
|
||||||
msgid "HSBC Swift MT940 statement export"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: field:banking.export.hsbc,state:0
|
|
||||||
#: field:banking.export.hsbc.wizard,state:0
|
|
||||||
msgid "State"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: code:addons/account_banking_uk_hsbc/wizard/export_hsbc.py:355
|
|
||||||
#: view:banking.export.hsbc:0
|
|
||||||
#: view:banking.export.hsbc.wizard:0
|
|
||||||
#: model:ir.model,name:account_banking_uk_hsbc.model_banking_export_hsbc
|
|
||||||
#: model:ir.model,name:account_banking_uk_hsbc.model_banking_export_hsbc_wizard
|
|
||||||
#, python-format
|
|
||||||
msgid "HSBC Export"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: selection:banking.export.hsbc,state:0
|
|
||||||
msgid "Draft"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: view:banking.export.hsbc:0
|
|
||||||
msgid "Processing Information"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: code:addons/account_banking_uk_hsbc/hsbc_mt940.py:145
|
|
||||||
#, python-format
|
|
||||||
msgid " This format is available through\n"
|
|
||||||
" the HSBC web interface.\n"
|
|
||||||
" "
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: view:banking.export.hsbc:0
|
|
||||||
#: field:banking.export.hsbc,payment_order_ids:0
|
|
||||||
#: field:banking.export.hsbc.wizard,payment_order_ids:0
|
|
||||||
msgid "Payment Orders"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: selection:banking.export.hsbc,state:0
|
|
||||||
msgid "Sent"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: view:banking.export.hsbc.wizard:0
|
|
||||||
#: selection:banking.export.hsbc.wizard,state:0
|
|
||||||
msgid "Finish"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: field:banking.export.hsbc.wizard,test:0
|
|
||||||
msgid "unknown"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: field:banking.hsbc.clientid,company_id:0
|
|
||||||
msgid "Company"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: model:ir.model,name:account_banking_uk_hsbc.model_payment_order
|
|
||||||
msgid "Payment Order"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: field:banking.hsbc.clientid,clientid:0
|
|
||||||
msgid "Client ID"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: view:banking.export.hsbc.wizard:0
|
|
||||||
msgid "Reference for further communication"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: model:ir.model,name:account_banking_uk_hsbc.model_payment_line
|
|
||||||
msgid "Payment Line"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: view:banking.export.hsbc.wizard:0
|
|
||||||
msgid "Processing Details"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: selection:banking.export.hsbc.wizard,state:0
|
|
||||||
msgid "Create"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: code:addons/account_banking_uk_hsbc/hsbc_mt940.py:138
|
|
||||||
#, python-format
|
|
||||||
msgid "Import error"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: field:banking.export.hsbc,no_transactions:0
|
|
||||||
#: field:banking.export.hsbc.wizard,no_transactions:0
|
|
||||||
msgid "Number of Transactions"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: field:banking.hsbc.clientid,name:0
|
|
||||||
msgid "Name"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: field:banking.export.hsbc,execution_date:0
|
|
||||||
#: field:banking.export.hsbc.wizard,execution_date_create:0
|
|
||||||
#: field:banking.export.hsbc.wizard,execution_date_finish:0
|
|
||||||
msgid "Execution Date"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: code:addons/account_banking_uk_hsbc/wizard/export_hsbc.py:266
|
|
||||||
#, python-format
|
|
||||||
msgid "Transaction invalid: "
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: field:banking.export.hsbc,total_amount:0
|
|
||||||
#: field:banking.export.hsbc.wizard,total_amount:0
|
|
||||||
msgid "Total Amount"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: help:banking.export.hsbc.wizard,reference:0
|
|
||||||
msgid "The bank will use this reference in feedback communication to refer to this run. 35 characters are available."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: code:addons/account_banking_uk_hsbc/wizard/export_hsbc.py:315
|
|
||||||
#, python-format
|
|
||||||
msgid "Batch invalid: "
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: code:addons/account_banking_uk_hsbc/wizard/export_hsbc.py:167
|
|
||||||
#: code:addons/account_banking_uk_hsbc/wizard/export_hsbc.py:185
|
|
||||||
#: code:addons/account_banking_uk_hsbc/wizard/export_hsbc.py:210
|
|
||||||
#: code:addons/account_banking_uk_hsbc/wizard/export_hsbc.py:233
|
|
||||||
#: code:addons/account_banking_uk_hsbc/wizard/export_hsbc.py:265
|
|
||||||
#: code:addons/account_banking_uk_hsbc/wizard/export_hsbc.py:286
|
|
||||||
#: code:addons/account_banking_uk_hsbc/wizard/export_hsbc.py:292
|
|
||||||
#: code:addons/account_banking_uk_hsbc/wizard/export_hsbc.py:314
|
|
||||||
#, python-format
|
|
||||||
msgid "Error"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: field:banking.export.hsbc.wizard,file_id:0
|
|
||||||
msgid "hsbc File"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: view:banking.export.hsbc.wizard:0
|
|
||||||
msgid "Additional message for all transactions"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: view:banking.hsbc.clientid:0
|
|
||||||
#: model:ir.actions.act_window,name:account_banking_uk_hsbc.banking_hsbc_clientid_action
|
|
||||||
#: model:ir.model,name:account_banking_uk_hsbc.model_banking_hsbc_clientid
|
|
||||||
#: model:ir.ui.menu,name:account_banking_uk_hsbc.banking_hsbc_clientid_menu
|
|
||||||
#: field:payment.order,hsbc_clientid_id:0
|
|
||||||
msgid "HSBC Client ID"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: selection:banking.export.hsbc,state:0
|
|
||||||
msgid "Reconciled"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: field:banking.export.hsbc.wizard,reference:0
|
|
||||||
msgid "Reference"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: view:banking.export.hsbc:0
|
|
||||||
msgid "Payment order"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: code:addons/account_banking_uk_hsbc/wizard/export_hsbc.py:294
|
|
||||||
#, python-format
|
|
||||||
msgid "Your company's bank account has to have a valid UK account number (not IBAN)"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: field:banking.export.hsbc,date_generated:0
|
|
||||||
msgid "Generation Date"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: view:banking.export.hsbc:0
|
|
||||||
msgid "General Information"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: view:banking.export.hsbc.wizard:0
|
|
||||||
msgid "Export"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: field:banking.export.hsbc.wizard,file:0
|
|
||||||
msgid "File"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: code:addons/account_banking_uk_hsbc/wizard/export_hsbc.py:287
|
|
||||||
#, python-format
|
|
||||||
msgid "Source account invalid: "
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: view:banking.export.hsbc.wizard:0
|
|
||||||
msgid "Cancel"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: field:banking.export.hsbc,identification:0
|
|
||||||
msgid "Identification"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: field:banking.export.hsbc,file:0
|
|
||||||
msgid "HSBC File"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#. module: account_banking_uk_hsbc
|
|
||||||
#: model:ir.actions.act_window,name:account_banking_uk_hsbc.act_banking_export_hsbc_payment_order
|
|
||||||
#: model:ir.actions.act_window,name:account_banking_uk_hsbc.action_account_banking_hsbcs
|
|
||||||
#: model:ir.ui.menu,name:account_banking_uk_hsbc.menu_action_account_banking_exported_hsbc_files
|
|
||||||
msgid "Generated HSBC files"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
"""
|
|
||||||
Parser for HSBC UK MT940 format files
|
|
||||||
Based on fi_patu's parser
|
|
||||||
"""
|
|
||||||
import re
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
|
|
||||||
class HSBCParser(object):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
recparse = dict()
|
|
||||||
patterns = {'ebcdic': "\w/\?:\(\).,'+{} -"}
|
|
||||||
|
|
||||||
# MT940 header
|
|
||||||
recparse["20"] = ":(?P<recordid>20):(?P<transref>.{1,16})"
|
|
||||||
recparse["25"] = (":(?P<recordid>25):(?P<sortcode>\d{6})"
|
|
||||||
"(?P<accnum>\d{1,29})")
|
|
||||||
recparse["28"] = ":(?P<recordid>28C?):(?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<bookingdate>\d{4})?\
|
|
||||||
(?P<creditmarker>R?[CD])\
|
|
||||||
(?P<currency>[A-Z])?\
|
|
||||||
(?P<amount>[\d,]{1,15})\
|
|
||||||
(?P<bookingcode>[A-Z][A-Z0-9]{3})\
|
|
||||||
(?P<custrefno>[%(ebcdic)s]{1,16})\
|
|
||||||
(?://)\
|
|
||||||
(?P<bankref>[%(ebcdic)s]{1,16})?\
|
|
||||||
(?:\n(?P<furtherinfo>[%(ebcdic)s]))?\
|
|
||||||
""" % (patterns)
|
|
||||||
|
|
||||||
# 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 "valuedate" in matchdict):
|
|
||||||
# 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):
|
|
||||||
with open(filename, "r") as hsbcfile:
|
|
||||||
HSBCParser().parse(hsbcfile.readlines())
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""The main function, currently just calls a dummy filename
|
|
||||||
|
|
||||||
:returns: description
|
|
||||||
"""
|
|
||||||
parse_file("testfile")
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
|
|
||||||
"access_banking_hsbc_clientid","banking.hsbc.clientid","model_banking_hsbc_clientid","account_payment.group_account_payment",1,1,1,1
|
|
||||||
"access_banking_export_hsbc","banking.export.hsbc","model_banking_export_hsbc","account_payment.group_account_payment",1,1,1,1
|
|
||||||
|
@@ -1,23 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
|
||||||
# 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
from . import export_hsbc
|
|
||||||
@@ -1,428 +0,0 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
|
||||||
# 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 base64
|
|
||||||
from datetime import datetime, date
|
|
||||||
from decimal import Decimal
|
|
||||||
import paymul
|
|
||||||
import string
|
|
||||||
import random
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from openerp.osv import orm, fields
|
|
||||||
from openerp.tools import ustr
|
|
||||||
from openerp.tools.translate import _
|
|
||||||
|
|
||||||
|
|
||||||
def strpdate(arg, format='%Y-%m-%d'):
|
|
||||||
"""shortcut"""
|
|
||||||
return datetime.strptime(arg, format).date()
|
|
||||||
|
|
||||||
|
|
||||||
def strfdate(arg, format='%Y-%m-%d'):
|
|
||||||
"""shortcut"""
|
|
||||||
return arg.strftime(format)
|
|
||||||
|
|
||||||
|
|
||||||
class banking_export_hsbc_wizard(orm.TransientModel):
|
|
||||||
_name = 'banking.export.hsbc.wizard'
|
|
||||||
_description = 'HSBC Export'
|
|
||||||
_columns = {
|
|
||||||
'state': fields.selection(
|
|
||||||
[
|
|
||||||
('create', 'Create'),
|
|
||||||
('finish', 'Finish')
|
|
||||||
],
|
|
||||||
'State',
|
|
||||||
readonly=True,
|
|
||||||
),
|
|
||||||
'test': fields.boolean(),
|
|
||||||
'reference': fields.char(
|
|
||||||
'Reference', size=35,
|
|
||||||
help=('The bank will use this reference in feedback communication '
|
|
||||||
'to refer to this run. 35 characters are available.'
|
|
||||||
),
|
|
||||||
),
|
|
||||||
'execution_date_create': fields.date(
|
|
||||||
'Execution Date',
|
|
||||||
help=('This is the date the file should be processed by the bank. '
|
|
||||||
'Don\'t choose a date beyond the nearest date in your '
|
|
||||||
'payments. The latest allowed date is 30 days from now.\n'
|
|
||||||
'Please keep in mind that banks only execute on working '
|
|
||||||
'days and typically use a delay of two days between '
|
|
||||||
'execution date and effective transfer date.'
|
|
||||||
),
|
|
||||||
),
|
|
||||||
'file_id': fields.many2one(
|
|
||||||
'banking.export.hsbc',
|
|
||||||
'hsbc File',
|
|
||||||
readonly=True
|
|
||||||
),
|
|
||||||
'file': fields.related(
|
|
||||||
'file_id', 'file', type='binary',
|
|
||||||
readonly=True,
|
|
||||||
string='File',
|
|
||||||
),
|
|
||||||
'execution_date_finish': fields.related(
|
|
||||||
'file_id', 'execution_date', type='date',
|
|
||||||
readonly=True,
|
|
||||||
string='Execution Date',
|
|
||||||
),
|
|
||||||
'total_amount': fields.related(
|
|
||||||
'file_id', 'total_amount',
|
|
||||||
type='float',
|
|
||||||
string='Total Amount',
|
|
||||||
readonly=True,
|
|
||||||
),
|
|
||||||
'no_transactions': fields.integer(
|
|
||||||
'Number of Transactions',
|
|
||||||
readonly=True,
|
|
||||||
),
|
|
||||||
'payment_order_ids': fields.many2many(
|
|
||||||
'payment.order', 'rel_wiz_payorders', 'wizard_id',
|
|
||||||
'payment_order_id', 'Payment Orders',
|
|
||||||
readonly=True,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
logger = logging.getLogger('export_hsbc')
|
|
||||||
|
|
||||||
def create(self, cursor, uid, wizard_data, context=None):
|
|
||||||
'''
|
|
||||||
Retrieve a sane set of default values based on the payment orders
|
|
||||||
from the context.
|
|
||||||
'''
|
|
||||||
|
|
||||||
if 'execution_date_create' not in wizard_data:
|
|
||||||
po_ids = context.get('active_ids', [])
|
|
||||||
po_model = self.pool.get('payment.order')
|
|
||||||
pos = po_model.browse(cursor, uid, po_ids)
|
|
||||||
|
|
||||||
execution_date = date.today()
|
|
||||||
|
|
||||||
for po in pos:
|
|
||||||
if po.date_prefered == 'fixed' and po.date_planned:
|
|
||||||
execution_date = strpdate(po.date_planned)
|
|
||||||
elif po.date_prefered == 'due':
|
|
||||||
for line in po.line_ids:
|
|
||||||
if line.move_line_id.date_maturity:
|
|
||||||
date_maturity = strpdate(
|
|
||||||
line.move_line_id.date_maturity
|
|
||||||
)
|
|
||||||
if date_maturity < execution_date:
|
|
||||||
execution_date = date_maturity
|
|
||||||
|
|
||||||
execution_date = max(execution_date, date.today())
|
|
||||||
|
|
||||||
# The default reference contains a /, which is invalid for PAYMUL
|
|
||||||
reference = pos[0].reference.replace('/', ' ')
|
|
||||||
|
|
||||||
wizard_data.update({
|
|
||||||
'execution_date_create': strfdate(execution_date),
|
|
||||||
'reference': reference,
|
|
||||||
'payment_order_ids': [(6, 0, po_ids)],
|
|
||||||
'state': 'create',
|
|
||||||
})
|
|
||||||
|
|
||||||
return super(banking_export_hsbc_wizard, self).create(
|
|
||||||
cursor, uid, wizard_data, context)
|
|
||||||
|
|
||||||
def _create_account(self, oe_account, origin_country=None,
|
|
||||||
is_origin_account=False):
|
|
||||||
# let the receiving bank select the currency from the batch
|
|
||||||
currency = None
|
|
||||||
holder = oe_account.owner_name or oe_account.partner_id.name
|
|
||||||
self.logger.info('Create account %s' % (holder))
|
|
||||||
self.logger.info('-- %s' % (oe_account.country_id.code))
|
|
||||||
self.logger.info('-- %s' % (oe_account.acc_number))
|
|
||||||
|
|
||||||
if oe_account.state == 'iban':
|
|
||||||
self.logger.info('IBAN: %s' % (oe_account.acc_number))
|
|
||||||
paymul_account = paymul.IBANAccount(
|
|
||||||
iban=oe_account.acc_number,
|
|
||||||
bic=oe_account.bank.bic,
|
|
||||||
holder=holder,
|
|
||||||
currency=currency,
|
|
||||||
)
|
|
||||||
transaction_kwargs = {
|
|
||||||
'charges': paymul.CHARGES_EACH_OWN,
|
|
||||||
}
|
|
||||||
elif oe_account.country_id.code == 'GB':
|
|
||||||
self.logger.info('GB: %s %s' % (oe_account.country_id.code,
|
|
||||||
oe_account.acc_number))
|
|
||||||
split = oe_account.acc_number.split(" ", 2)
|
|
||||||
if len(split) == 2:
|
|
||||||
sortcode, accountno = split
|
|
||||||
else:
|
|
||||||
raise orm.except_orm(
|
|
||||||
_('Error'),
|
|
||||||
"Invalid GB acccount number '%s'" % oe_account.acc_number)
|
|
||||||
paymul_account = paymul.UKAccount(
|
|
||||||
number=accountno,
|
|
||||||
sortcode=sortcode,
|
|
||||||
holder=holder,
|
|
||||||
currency=currency,
|
|
||||||
)
|
|
||||||
transaction_kwargs = {
|
|
||||||
'charges': paymul.CHARGES_PAYEE,
|
|
||||||
}
|
|
||||||
elif oe_account.country_id.code in ('US', 'CA'):
|
|
||||||
self.logger.info('US/CA: %s %s' % (oe_account.country_id.code,
|
|
||||||
oe_account.acc_number))
|
|
||||||
split = oe_account.acc_number.split(' ', 2)
|
|
||||||
if len(split) == 2:
|
|
||||||
sortcode, accountno = split
|
|
||||||
else:
|
|
||||||
raise orm.except_orm(
|
|
||||||
_('Error'),
|
|
||||||
_("Invalid %s account number '%s'") %
|
|
||||||
(oe_account.country_id.code, oe_account.acc_number))
|
|
||||||
paymul_account = paymul.NorthAmericanAccount(
|
|
||||||
number=accountno,
|
|
||||||
sortcode=sortcode,
|
|
||||||
holder=holder,
|
|
||||||
currency=currency,
|
|
||||||
swiftcode=oe_account.bank.bic,
|
|
||||||
country=oe_account.country_id.code,
|
|
||||||
origin_country=origin_country,
|
|
||||||
is_origin_account=is_origin_account
|
|
||||||
)
|
|
||||||
transaction_kwargs = {
|
|
||||||
'charges': paymul.CHARGES_PAYEE,
|
|
||||||
}
|
|
||||||
transaction_kwargs = {
|
|
||||||
'charges': paymul.CHARGES_PAYEE,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
self.logger.info('SWIFT Account: %s' % oe_account.country_id.code)
|
|
||||||
split = oe_account.acc_number.split(' ', 2)
|
|
||||||
if len(split) == 2:
|
|
||||||
sortcode, accountno = split
|
|
||||||
else:
|
|
||||||
raise orm.except_orm(
|
|
||||||
_('Error'),
|
|
||||||
_("Invalid %s account number '%s'") %
|
|
||||||
(oe_account.country_id.code, oe_account.acc_number))
|
|
||||||
paymul_account = paymul.SWIFTAccount(
|
|
||||||
number=accountno,
|
|
||||||
sortcode=sortcode,
|
|
||||||
holder=holder,
|
|
||||||
currency=currency,
|
|
||||||
swiftcode=oe_account.bank.bic,
|
|
||||||
country=oe_account.country_id.code,
|
|
||||||
)
|
|
||||||
transaction_kwargs = {
|
|
||||||
'charges': paymul.CHARGES_PAYEE,
|
|
||||||
}
|
|
||||||
transaction_kwargs = {
|
|
||||||
'charges': paymul.CHARGES_PAYEE,
|
|
||||||
}
|
|
||||||
|
|
||||||
return paymul_account, transaction_kwargs
|
|
||||||
|
|
||||||
def _create_transaction(self, line):
|
|
||||||
# Check on missing partner of bank account (this can happen!)
|
|
||||||
if not line.bank_id or not line.bank_id.partner_id:
|
|
||||||
raise orm.except_orm(
|
|
||||||
_('Error'),
|
|
||||||
_('There is insufficient information.\r\n'
|
|
||||||
'Both destination address and account '
|
|
||||||
'number must be provided')
|
|
||||||
)
|
|
||||||
|
|
||||||
self.logger.info('====')
|
|
||||||
dest_account, transaction_kwargs = self._create_account(
|
|
||||||
line.bank_id, line.order_id.mode.bank_id.country_id.code
|
|
||||||
)
|
|
||||||
|
|
||||||
means = {
|
|
||||||
'ACH or EZONE': paymul.MEANS_ACH_OR_EZONE,
|
|
||||||
'Faster Payment': paymul.MEANS_FASTER_PAYMENT,
|
|
||||||
'Priority Payment': paymul.MEANS_PRIORITY_PAYMENT
|
|
||||||
}.get(line.order_id.mode.type.name)
|
|
||||||
if means is None:
|
|
||||||
raise orm.except_orm(
|
|
||||||
_('Error'),
|
|
||||||
_("Invalid payment type mode for HSBC '%s'")
|
|
||||||
% line.order_id.mode.type.name
|
|
||||||
)
|
|
||||||
|
|
||||||
if not line.info_partner:
|
|
||||||
raise orm.except_orm(
|
|
||||||
_('Error'),
|
|
||||||
_("No default address for transaction '%s'") % line.name
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
return paymul.Transaction(
|
|
||||||
amount=Decimal(str(line.amount_currency)),
|
|
||||||
currency=line.currency.name,
|
|
||||||
account=dest_account,
|
|
||||||
means=means,
|
|
||||||
name_address=line.info_partner,
|
|
||||||
customer_reference=line.name,
|
|
||||||
payment_reference=line.name,
|
|
||||||
**transaction_kwargs
|
|
||||||
)
|
|
||||||
except ValueError as exc:
|
|
||||||
raise orm.except_orm(
|
|
||||||
_('Error'),
|
|
||||||
_('Transaction invalid: %s') + ustr(exc)
|
|
||||||
)
|
|
||||||
|
|
||||||
def wizard_export(self, cursor, uid, wizard_data_ids, context):
|
|
||||||
'''
|
|
||||||
Wizard to actually create the HSBC file
|
|
||||||
'''
|
|
||||||
|
|
||||||
wizard_data = self.browse(cursor, uid, wizard_data_ids, context)[0]
|
|
||||||
result_model = self.pool.get('banking.export.hsbc')
|
|
||||||
payment_orders = wizard_data.payment_order_ids
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.logger.info(
|
|
||||||
'Source - %s (%s) %s' % (
|
|
||||||
payment_orders[0].mode.bank_id.partner_id.name,
|
|
||||||
payment_orders[0].mode.bank_id.acc_number,
|
|
||||||
payment_orders[0].mode.bank_id.country_id.code)
|
|
||||||
)
|
|
||||||
src_account = self._create_account(
|
|
||||||
payment_orders[0].mode.bank_id,
|
|
||||||
payment_orders[0].mode.bank_id.country_id.code,
|
|
||||||
is_origin_account=True
|
|
||||||
)[0]
|
|
||||||
except ValueError as exc:
|
|
||||||
raise orm.except_orm(
|
|
||||||
_('Error'),
|
|
||||||
_('Source account invalid: ') + ustr(exc)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not isinstance(src_account, paymul.UKAccount):
|
|
||||||
raise orm.except_orm(
|
|
||||||
_('Error'),
|
|
||||||
_("Your company's bank account has to have a valid UK "
|
|
||||||
"account number (not IBAN)" + ustr(type(src_account)))
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.logger.info('Create transactions...')
|
|
||||||
transactions = []
|
|
||||||
hsbc_clientid = ''
|
|
||||||
for po in payment_orders:
|
|
||||||
transactions += [
|
|
||||||
self._create_transaction(l) for l in po.line_ids
|
|
||||||
]
|
|
||||||
hsbc_clientid = po.hsbc_clientid_id.clientid
|
|
||||||
|
|
||||||
batch = paymul.Batch(
|
|
||||||
exec_date=strpdate(wizard_data.execution_date_create),
|
|
||||||
reference=wizard_data.reference,
|
|
||||||
debit_account=src_account,
|
|
||||||
name_address=payment_orders[0].line_ids[0].info_owner,
|
|
||||||
)
|
|
||||||
batch.transactions = transactions
|
|
||||||
except ValueError as exc:
|
|
||||||
raise orm.except_orm(
|
|
||||||
_('Error'),
|
|
||||||
_('Batch invalid: ') + ustr(exc)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generate random identifier until an unused one is found
|
|
||||||
while True:
|
|
||||||
ref = ''.join(random.choice(string.ascii_uppercase + string.digits)
|
|
||||||
for x in range(15))
|
|
||||||
|
|
||||||
ids = result_model.search(cursor, uid, [
|
|
||||||
('identification', '=', ref)
|
|
||||||
])
|
|
||||||
|
|
||||||
if not ids:
|
|
||||||
break
|
|
||||||
|
|
||||||
message = paymul.Message(reference=ref)
|
|
||||||
message.batches.append(batch)
|
|
||||||
interchange = paymul.Interchange(client_id=hsbc_clientid,
|
|
||||||
reference=ref,
|
|
||||||
message=message)
|
|
||||||
|
|
||||||
export_result = {
|
|
||||||
'identification': interchange.reference,
|
|
||||||
'execution_date': batch.exec_date,
|
|
||||||
'total_amount': batch.amount(),
|
|
||||||
'no_transactions': len(batch.transactions),
|
|
||||||
'file': base64.encodestring(str(interchange)),
|
|
||||||
'payment_order_ids': [
|
|
||||||
[6, 0, [po.id for po in payment_orders]]
|
|
||||||
],
|
|
||||||
}
|
|
||||||
file_id = result_model.create(cursor, uid, export_result, context)
|
|
||||||
|
|
||||||
self.write(cursor, uid, [wizard_data_ids[0]], {
|
|
||||||
'file_id': file_id,
|
|
||||||
'no_transactions': len(batch.transactions),
|
|
||||||
'state': 'finish',
|
|
||||||
}, context)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'name': _('HSBC Export'),
|
|
||||||
'view_type': 'form',
|
|
||||||
'view_mode': 'form',
|
|
||||||
'res_model': self._name,
|
|
||||||
'domain': [],
|
|
||||||
'context': dict(context, active_ids=wizard_data_ids),
|
|
||||||
'type': 'ir.actions.act_window',
|
|
||||||
'target': 'new',
|
|
||||||
'res_id': wizard_data_ids[0] or False,
|
|
||||||
}
|
|
||||||
|
|
||||||
def wizard_cancel(self, cursor, uid, ids, context):
|
|
||||||
'''
|
|
||||||
Cancel the export: just drop the file
|
|
||||||
'''
|
|
||||||
|
|
||||||
wizard_data = self.browse(cursor, uid, ids, context)[0]
|
|
||||||
result_model = self.pool.get('banking.export.hsbc')
|
|
||||||
|
|
||||||
try:
|
|
||||||
result_model.unlink(cursor, uid, wizard_data.file_id.id)
|
|
||||||
except AttributeError:
|
|
||||||
# file_id missing, wizard storage gone, server was restarted
|
|
||||||
pass
|
|
||||||
|
|
||||||
return {'type': 'ir.actions.act_window_close'}
|
|
||||||
|
|
||||||
def wizard_save(self, cursor, uid, ids, context):
|
|
||||||
'''
|
|
||||||
Save the export: mark all payments in the file as 'sent'
|
|
||||||
'''
|
|
||||||
|
|
||||||
wizard_data = self.browse(cursor, uid, ids, context)[0]
|
|
||||||
result_model = self.pool.get('banking.export.hsbc')
|
|
||||||
po_model = self.pool.get('payment.order')
|
|
||||||
|
|
||||||
result_model.write(cursor, uid, [wizard_data.file_id.id],
|
|
||||||
{'state': 'sent'})
|
|
||||||
|
|
||||||
po_ids = [po.id for po in wizard_data.payment_order_ids]
|
|
||||||
po_model.action_sent(cursor, uid, po_ids)
|
|
||||||
|
|
||||||
return {'type': 'ir.actions.act_window_close'}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<openerp>
|
|
||||||
<data>
|
|
||||||
<record id="wizard_banking_export_wizard_view" model="ir.ui.view">
|
|
||||||
<field name="name">banking.export.hsbc.wizard.view</field>
|
|
||||||
<field name="model">banking.export.hsbc.wizard</field>
|
|
||||||
<field name="type">form</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<form string="HSBC Export">
|
|
||||||
<field name="state" invisible="True"/>
|
|
||||||
<group states="create">
|
|
||||||
<separator colspan="4" string="Processing Details"/>
|
|
||||||
<field name="execution_date_create"/>
|
|
||||||
<field name="test"/>
|
|
||||||
<separator colspan="4" string="Reference for further communication"/>
|
|
||||||
<field name="reference" colspan="2"/>
|
|
||||||
<separator colspan="4" string="Additional message for all transactions"/>
|
|
||||||
<newline/>
|
|
||||||
<button icon="gtk-close" special="cancel" string="Cancel"/>
|
|
||||||
<button icon="gtk-ok" string="Export" name="wizard_export" type="object"/>
|
|
||||||
</group>
|
|
||||||
<group states="finish">
|
|
||||||
<field name="total_amount"/>
|
|
||||||
<field name="no_transactions"/>
|
|
||||||
<field name="execution_date_finish"/>
|
|
||||||
<newline/>
|
|
||||||
<!--<field name="file_id"/>-->
|
|
||||||
<field name="file"/>
|
|
||||||
<newline/>
|
|
||||||
<button icon="gtk-close" string="Cancel" name="wizard_cancel" type="object"/>
|
|
||||||
<button icon="gtk-ok" string="Finish" name="wizard_save" type="object"/>
|
|
||||||
</group>
|
|
||||||
</form>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
</data>
|
|
||||||
</openerp>
|
|
||||||
@@ -1,686 +0,0 @@
|
|||||||
# -*- 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/>.
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
from account_banking import sepa
|
|
||||||
from decimal import Decimal
|
|
||||||
import datetime
|
|
||||||
import re
|
|
||||||
import unicodedata
|
|
||||||
|
|
||||||
from openerp.tools import ustr
|
|
||||||
|
|
||||||
|
|
||||||
def strip_accents(string):
|
|
||||||
res = unicodedata.normalize('NFKD', ustr(string))
|
|
||||||
res = res.encode('ASCII', 'ignore')
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def split_account_holder(holder):
|
|
||||||
holder_parts = holder.split("\n")
|
|
||||||
|
|
||||||
try:
|
|
||||||
line2 = holder_parts[1]
|
|
||||||
except IndexError:
|
|
||||||
line2 = ''
|
|
||||||
|
|
||||||
return holder_parts[0], line2
|
|
||||||
|
|
||||||
|
|
||||||
def address_truncate(name_address):
|
|
||||||
addr_line = name_address.upper().split("\n")[0:5]
|
|
||||||
addr_line = [s[:35] for s in addr_line]
|
|
||||||
return addr_line
|
|
||||||
|
|
||||||
|
|
||||||
def edifact_isalnum(s):
|
|
||||||
"""The standard says alphanumeric characters, but spaces are also
|
|
||||||
allowed
|
|
||||||
"""
|
|
||||||
return bool(re.match(r'^[A-Za-z0-9 ]*$', s))
|
|
||||||
|
|
||||||
|
|
||||||
def edifact_digits(val, digits=None, mindigits=None):
|
|
||||||
if digits is None:
|
|
||||||
digits = ''
|
|
||||||
if mindigits is None:
|
|
||||||
mindigits = digits
|
|
||||||
|
|
||||||
pattern = r'^[0-9]{' + str(mindigits) + ',' + str(digits) + r'}$'
|
|
||||||
return bool(re.match(pattern, str(val)))
|
|
||||||
|
|
||||||
|
|
||||||
def edifact_isalnum_size(val, digits):
|
|
||||||
pattern = r'^[A-Za-z0-9 ]{' + str(digits) + ',' + str(digits) + r'}$'
|
|
||||||
return bool(re.match(pattern, str(val)))
|
|
||||||
|
|
||||||
|
|
||||||
class HasCurrency(object):
|
|
||||||
def _get_currency(self):
|
|
||||||
return self._currency
|
|
||||||
|
|
||||||
def _set_currency(self, currency):
|
|
||||||
if currency is None:
|
|
||||||
self._currency = None
|
|
||||||
else:
|
|
||||||
if not len(currency) <= 3:
|
|
||||||
raise ValueError("Currency must be <= 3 characters long: %s" %
|
|
||||||
ustr(currency))
|
|
||||||
|
|
||||||
if not edifact_isalnum(currency):
|
|
||||||
raise ValueError("Currency must be alphanumeric: %s" %
|
|
||||||
ustr(currency))
|
|
||||||
|
|
||||||
self._currency = currency.upper()
|
|
||||||
|
|
||||||
currency = property(_get_currency, _set_currency)
|
|
||||||
|
|
||||||
|
|
||||||
class LogicalSection(object):
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
segments = self.segments()
|
|
||||||
|
|
||||||
def format_segment(segment):
|
|
||||||
return '+'.join(
|
|
||||||
[':'.join([str(strip_accents(y)) for y in x]) for x in segment]
|
|
||||||
) + "'"
|
|
||||||
|
|
||||||
return "\n".join([format_segment(s) for s in segments])
|
|
||||||
|
|
||||||
|
|
||||||
def _fii_segment(self, party_qualifier):
|
|
||||||
holder = split_account_holder(self.holder)
|
|
||||||
account_identification = [self.number.replace(' ', ''), holder[0]]
|
|
||||||
if holder[1] or self.currency:
|
|
||||||
account_identification.append(holder[1])
|
|
||||||
if self.currency:
|
|
||||||
account_identification.append(self.currency)
|
|
||||||
return [
|
|
||||||
['FII'],
|
|
||||||
[party_qualifier],
|
|
||||||
account_identification,
|
|
||||||
self.institution_identification,
|
|
||||||
[self.country],
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class UKAccount(HasCurrency):
|
|
||||||
def _get_number(self):
|
|
||||||
return self._number
|
|
||||||
|
|
||||||
def _set_number(self, number):
|
|
||||||
if not edifact_digits(number, 8):
|
|
||||||
raise ValueError("Account number must be 8 digits long: " +
|
|
||||||
str(number))
|
|
||||||
|
|
||||||
self._number = number
|
|
||||||
|
|
||||||
number = property(_get_number, _set_number)
|
|
||||||
|
|
||||||
def _get_sortcode(self):
|
|
||||||
return self._sortcode
|
|
||||||
|
|
||||||
def _set_sortcode(self, sortcode):
|
|
||||||
if not edifact_digits(sortcode, 6):
|
|
||||||
raise ValueError("Account sort code must be 6 digits long: %s" %
|
|
||||||
ustr(sortcode))
|
|
||||||
|
|
||||||
self._sortcode = sortcode
|
|
||||||
|
|
||||||
sortcode = property(_get_sortcode, _set_sortcode)
|
|
||||||
|
|
||||||
def _get_holder(self):
|
|
||||||
return self._holder
|
|
||||||
|
|
||||||
def _set_holder(self, holder):
|
|
||||||
holder_parts = split_account_holder(holder)
|
|
||||||
|
|
||||||
if not len(holder_parts[0]) <= 35:
|
|
||||||
raise ValueError("Account holder must be <= 35 characters long: %s"
|
|
||||||
% ustr(holder_parts[0]))
|
|
||||||
|
|
||||||
if not len(holder_parts[1]) <= 35:
|
|
||||||
raise ValueError("Second line of account holder must be <= 35 "
|
|
||||||
"characters long: %s" % ustr(holder_parts[1]))
|
|
||||||
|
|
||||||
if not edifact_isalnum(holder_parts[0]):
|
|
||||||
raise ValueError("Account holder must be alphanumeric: %s" %
|
|
||||||
ustr(holder_parts[0]))
|
|
||||||
|
|
||||||
if not edifact_isalnum(holder_parts[1]):
|
|
||||||
raise ValueError("Second line of account holder must be "
|
|
||||||
"alphanumeric: %s" % ustr(holder_parts[1]))
|
|
||||||
|
|
||||||
self._holder = holder.upper()
|
|
||||||
|
|
||||||
holder = property(_get_holder, _set_holder)
|
|
||||||
|
|
||||||
def __init__(self, number, holder, currency, sortcode):
|
|
||||||
self.number = number
|
|
||||||
self.holder = holder
|
|
||||||
self.currency = currency
|
|
||||||
self.sortcode = sortcode
|
|
||||||
self.country = 'GB'
|
|
||||||
self.institution_identification = ['', '', '', self.sortcode, 154, 133]
|
|
||||||
|
|
||||||
def fii_bf_segment(self):
|
|
||||||
return _fii_segment(self, 'BF')
|
|
||||||
|
|
||||||
def fii_or_segment(self):
|
|
||||||
return _fii_segment(self, 'OR')
|
|
||||||
|
|
||||||
|
|
||||||
class NorthAmericanAccount(UKAccount):
|
|
||||||
|
|
||||||
def _set_account_ident(self):
|
|
||||||
if self.origin_country in ('US', 'CA'):
|
|
||||||
# Use the routing number
|
|
||||||
account_ident = ['', '', '', self.sortcode, 155, 114]
|
|
||||||
else:
|
|
||||||
# Using the BIC/Swift Code
|
|
||||||
account_ident = [self.bic, 25, 5, '', '', '']
|
|
||||||
return account_ident
|
|
||||||
|
|
||||||
def _set_sortcode(self, sortcode):
|
|
||||||
if self.origin_country == 'CA' and self.is_origin_account:
|
|
||||||
expected_digits = 6
|
|
||||||
else:
|
|
||||||
expected_digits = 9
|
|
||||||
if not edifact_digits(sortcode, expected_digits):
|
|
||||||
raise ValueError("Account routing number must be %d digits long: "
|
|
||||||
"%s" % (expected_digits, ustr(sortcode)))
|
|
||||||
|
|
||||||
self._sortcode = sortcode
|
|
||||||
|
|
||||||
def _get_sortcode(self):
|
|
||||||
return self._sortcode
|
|
||||||
|
|
||||||
sortcode = property(_get_sortcode, _set_sortcode)
|
|
||||||
|
|
||||||
def _set_bic(self, bic):
|
|
||||||
if (not edifact_isalnum_size(bic, 8)
|
|
||||||
and not edifact_isalnum_size(bic, 11)):
|
|
||||||
raise ValueError("Account BIC/Swift code must be 8 or 11 "
|
|
||||||
"characters long: %s" % ustr(bic))
|
|
||||||
self._bic = bic
|
|
||||||
|
|
||||||
def _get_bic(self):
|
|
||||||
return self._bic
|
|
||||||
|
|
||||||
bic = property(_get_bic, _set_bic)
|
|
||||||
|
|
||||||
def _set_number(self, number):
|
|
||||||
if not edifact_digits(number, mindigits=1):
|
|
||||||
raise ValueError("Account number is invalid: %s" % ustr(number))
|
|
||||||
|
|
||||||
self._number = number
|
|
||||||
|
|
||||||
def _get_number(self):
|
|
||||||
return self._number
|
|
||||||
|
|
||||||
number = property(_get_number, _set_number)
|
|
||||||
|
|
||||||
def __init__(self, number, holder, currency, sortcode, swiftcode, country,
|
|
||||||
origin_country=None, is_origin_account=False):
|
|
||||||
self.origin_country = origin_country
|
|
||||||
self.is_origin_account = is_origin_account
|
|
||||||
self.number = number
|
|
||||||
self.holder = holder
|
|
||||||
self.currency = currency
|
|
||||||
self.sortcode = sortcode
|
|
||||||
self.country = country
|
|
||||||
self.bic = swiftcode
|
|
||||||
self.institution_identification = self._set_account_ident()
|
|
||||||
|
|
||||||
|
|
||||||
class SWIFTAccount(UKAccount):
|
|
||||||
|
|
||||||
def _set_account_ident(self):
|
|
||||||
# Using the BIC/Swift Code
|
|
||||||
return [self.bic, 25, 5, '', '', '']
|
|
||||||
|
|
||||||
def _set_sortcode(self, sortcode):
|
|
||||||
self._sortcode = sortcode
|
|
||||||
|
|
||||||
def _get_sortcode(self):
|
|
||||||
return self._sortcode
|
|
||||||
|
|
||||||
sortcode = property(_get_sortcode, _set_sortcode)
|
|
||||||
|
|
||||||
def _set_bic(self, bic):
|
|
||||||
if (not edifact_isalnum_size(bic, 8)
|
|
||||||
and not edifact_isalnum_size(bic, 11)):
|
|
||||||
raise ValueError("Account BIC/Swift code must be 8 or 11 "
|
|
||||||
"characters long: %s" % ustr(bic))
|
|
||||||
self._bic = bic
|
|
||||||
|
|
||||||
def _get_bic(self):
|
|
||||||
return self._bic
|
|
||||||
|
|
||||||
bic = property(_get_bic, _set_bic)
|
|
||||||
|
|
||||||
def _set_number(self, number):
|
|
||||||
if not edifact_digits(number, mindigits=1):
|
|
||||||
raise ValueError("Account number is invalid: %s" %
|
|
||||||
ustr(number))
|
|
||||||
|
|
||||||
self._number = number
|
|
||||||
|
|
||||||
def _get_number(self):
|
|
||||||
return self._number
|
|
||||||
|
|
||||||
number = property(_get_number, _set_number)
|
|
||||||
|
|
||||||
def __init__(self, number, holder, currency, sortcode, swiftcode, country,
|
|
||||||
origin_country=None, is_origin_account=False):
|
|
||||||
self.origin_country = origin_country
|
|
||||||
self.is_origin_account = is_origin_account
|
|
||||||
self.number = number
|
|
||||||
self.holder = holder
|
|
||||||
self.currency = currency
|
|
||||||
self.sortcode = sortcode
|
|
||||||
self.country = country
|
|
||||||
self.bic = swiftcode
|
|
||||||
self.institution_identification = self._set_account_ident()
|
|
||||||
|
|
||||||
|
|
||||||
class IBANAccount(HasCurrency):
|
|
||||||
def _get_iban(self):
|
|
||||||
return self._iban
|
|
||||||
|
|
||||||
def _set_iban(self, iban):
|
|
||||||
iban_obj = sepa.IBAN(iban)
|
|
||||||
if not iban_obj.valid:
|
|
||||||
raise ValueError("IBAN is invalid: %s" % ustr(iban))
|
|
||||||
|
|
||||||
self._iban = iban
|
|
||||||
self.country = iban_obj.countrycode
|
|
||||||
|
|
||||||
iban = property(_get_iban, _set_iban)
|
|
||||||
|
|
||||||
def __init__(self, iban, bic, currency, holder):
|
|
||||||
self.iban = iban
|
|
||||||
self.number = iban
|
|
||||||
self.bic = bic
|
|
||||||
self.currency = currency
|
|
||||||
self.holder = holder
|
|
||||||
self.institution_identification = [self.bic, 25, 5, '', '', '']
|
|
||||||
|
|
||||||
def fii_bf_segment(self):
|
|
||||||
return _fii_segment(self, 'BF')
|
|
||||||
|
|
||||||
|
|
||||||
class Interchange(LogicalSection):
|
|
||||||
def _get_reference(self):
|
|
||||||
return self._reference
|
|
||||||
|
|
||||||
def _set_reference(self, reference):
|
|
||||||
if not len(reference) <= 15:
|
|
||||||
raise ValueError("Reference must be <= 15 characters long: %s" %
|
|
||||||
ustr(reference))
|
|
||||||
|
|
||||||
if not edifact_isalnum(reference):
|
|
||||||
raise ValueError("Reference must be alphanumeric: %s" %
|
|
||||||
ustr(reference))
|
|
||||||
|
|
||||||
self._reference = reference.upper()
|
|
||||||
|
|
||||||
reference = property(_get_reference, _set_reference)
|
|
||||||
|
|
||||||
def __init__(self, client_id, reference, create_dt=None, message=None):
|
|
||||||
self.client_id = client_id
|
|
||||||
self.create_dt = create_dt or datetime.datetime.now()
|
|
||||||
self.reference = reference
|
|
||||||
self.message = message
|
|
||||||
|
|
||||||
def segments(self):
|
|
||||||
segments = []
|
|
||||||
segments.append([
|
|
||||||
['UNB'],
|
|
||||||
['UNOA', 3],
|
|
||||||
['', '', self.client_id],
|
|
||||||
['', '', 'HEXAGON ABC'],
|
|
||||||
[self.create_dt.strftime('%y%m%d'),
|
|
||||||
self.create_dt.strftime('%H%M')],
|
|
||||||
[self.reference],
|
|
||||||
])
|
|
||||||
segments += self.message.segments()
|
|
||||||
segments.append([
|
|
||||||
['UNZ'],
|
|
||||||
[1],
|
|
||||||
[self.reference],
|
|
||||||
])
|
|
||||||
return segments
|
|
||||||
|
|
||||||
|
|
||||||
class Message(LogicalSection):
|
|
||||||
def _get_reference(self):
|
|
||||||
return self._reference
|
|
||||||
|
|
||||||
def _set_reference(self, reference):
|
|
||||||
if not len(reference) <= 35:
|
|
||||||
raise ValueError("Reference must be <= 35 characters long: %s" %
|
|
||||||
ustr(reference))
|
|
||||||
|
|
||||||
if not edifact_isalnum(reference):
|
|
||||||
raise ValueError("Reference must be alphanumeric: %s" %
|
|
||||||
ustr(reference))
|
|
||||||
|
|
||||||
self._reference = reference.upper()
|
|
||||||
|
|
||||||
reference = property(_get_reference, _set_reference)
|
|
||||||
|
|
||||||
def __init__(self, reference, dt=None):
|
|
||||||
if dt:
|
|
||||||
self.dt = dt
|
|
||||||
else:
|
|
||||||
self.dt = datetime.datetime.now()
|
|
||||||
|
|
||||||
self.reference = reference
|
|
||||||
self.batches = []
|
|
||||||
|
|
||||||
def segments(self):
|
|
||||||
# HSBC only accepts one message per interchange
|
|
||||||
message_reference_number = 1
|
|
||||||
|
|
||||||
segments = []
|
|
||||||
|
|
||||||
segments.append([
|
|
||||||
['UNH'],
|
|
||||||
[message_reference_number],
|
|
||||||
['PAYMUL', 'D', '96A', 'UN', 'FUN01G'],
|
|
||||||
])
|
|
||||||
segments.append([
|
|
||||||
['BGM'],
|
|
||||||
[452],
|
|
||||||
[self.reference],
|
|
||||||
[9],
|
|
||||||
])
|
|
||||||
segments.append([
|
|
||||||
['DTM'],
|
|
||||||
(137, self.dt.strftime('%Y%m%d'), 102),
|
|
||||||
])
|
|
||||||
for index, batch in enumerate(self.batches):
|
|
||||||
segments += batch.segments(index + 1)
|
|
||||||
segments.append([
|
|
||||||
['CNT'],
|
|
||||||
['39', sum([len(x.transactions) for x in self.batches])],
|
|
||||||
])
|
|
||||||
segments.append([
|
|
||||||
['UNT'],
|
|
||||||
[len(segments) + 1],
|
|
||||||
[message_reference_number]
|
|
||||||
])
|
|
||||||
|
|
||||||
return segments
|
|
||||||
|
|
||||||
|
|
||||||
class Batch(LogicalSection):
|
|
||||||
def _get_reference(self):
|
|
||||||
return self._reference
|
|
||||||
|
|
||||||
def _set_reference(self, reference):
|
|
||||||
if not len(reference) <= 18:
|
|
||||||
raise ValueError("Reference must be <= 18 characters long: %s" %
|
|
||||||
ustr(reference))
|
|
||||||
|
|
||||||
if not edifact_isalnum(reference):
|
|
||||||
raise ValueError("Reference must be alphanumeric: %s" %
|
|
||||||
ustr(reference))
|
|
||||||
|
|
||||||
self._reference = reference.upper()
|
|
||||||
|
|
||||||
reference = property(_get_reference, _set_reference)
|
|
||||||
|
|
||||||
def __init__(self, exec_date, reference, debit_account, name_address):
|
|
||||||
self.exec_date = exec_date
|
|
||||||
self.reference = reference
|
|
||||||
self.debit_account = debit_account
|
|
||||||
self.name_address = name_address
|
|
||||||
self.transactions = []
|
|
||||||
|
|
||||||
def amount(self):
|
|
||||||
return sum([x.amount for x in self.transactions])
|
|
||||||
|
|
||||||
def segments(self, index):
|
|
||||||
if not edifact_digits(index, 6, 1):
|
|
||||||
raise ValueError("Index must be 6 digits or less: " + str(index))
|
|
||||||
|
|
||||||
# Store the payment means
|
|
||||||
means = None
|
|
||||||
if len(self.transactions) > 0:
|
|
||||||
means = self.transactions[0].means
|
|
||||||
|
|
||||||
segments = []
|
|
||||||
|
|
||||||
if means != MEANS_PRIORITY_PAYMENT:
|
|
||||||
segments.append([
|
|
||||||
['LIN'],
|
|
||||||
[index],
|
|
||||||
])
|
|
||||||
segments.append([
|
|
||||||
['DTM'],
|
|
||||||
[203, self.exec_date.strftime('%Y%m%d'), 102],
|
|
||||||
])
|
|
||||||
segments.append([
|
|
||||||
['RFF'],
|
|
||||||
['AEK', self.reference],
|
|
||||||
])
|
|
||||||
|
|
||||||
currencies = set([x.currency for x in self.transactions])
|
|
||||||
if len(currencies) > 1:
|
|
||||||
raise ValueError("All transactions in a batch must have the "
|
|
||||||
"same currency")
|
|
||||||
|
|
||||||
segments.append([
|
|
||||||
['MOA'],
|
|
||||||
[9, self.amount().quantize(Decimal('0.00')), currencies.pop()],
|
|
||||||
])
|
|
||||||
segments.append(self.debit_account.fii_or_segment())
|
|
||||||
segments.append([
|
|
||||||
['NAD'],
|
|
||||||
['OY'],
|
|
||||||
[''],
|
|
||||||
address_truncate(self.name_address),
|
|
||||||
])
|
|
||||||
|
|
||||||
for index, transaction in enumerate(self.transactions):
|
|
||||||
if transaction.means == MEANS_PRIORITY_PAYMENT:
|
|
||||||
# Need a debit-credit format for Priority Payments
|
|
||||||
segments.append([
|
|
||||||
['LIN'],
|
|
||||||
[index+1],
|
|
||||||
])
|
|
||||||
segments.append([
|
|
||||||
['DTM'],
|
|
||||||
[203, self.exec_date.strftime('%Y%m%d'), 102],
|
|
||||||
])
|
|
||||||
segments.append([
|
|
||||||
['RFF'],
|
|
||||||
['AEK', self.reference],
|
|
||||||
])
|
|
||||||
|
|
||||||
# Use the transaction amount and currency for the debit line
|
|
||||||
segments.append([
|
|
||||||
['MOA'],
|
|
||||||
[9, transaction.amount.quantize(Decimal('0.00')),
|
|
||||||
transaction.currency],
|
|
||||||
])
|
|
||||||
segments.append(self.debit_account.fii_or_segment())
|
|
||||||
segments.append([
|
|
||||||
['NAD'],
|
|
||||||
['OY'],
|
|
||||||
[''],
|
|
||||||
address_truncate(self.name_address),
|
|
||||||
])
|
|
||||||
use_index = 1
|
|
||||||
else:
|
|
||||||
use_index = index + 1
|
|
||||||
|
|
||||||
segments += transaction.segments(use_index)
|
|
||||||
|
|
||||||
return segments
|
|
||||||
|
|
||||||
# From the spec for FCA segments:
|
|
||||||
# 13 = All charges borne by payee (or beneficiary)
|
|
||||||
# 14 = Each pay own cost
|
|
||||||
# 15 = All charges borne by payor (or ordering customer)
|
|
||||||
# For Faster Payments this should always be ‘14’
|
|
||||||
# Where this field is not present, “14” will be used as a default.
|
|
||||||
CHARGES_PAYEE = 13
|
|
||||||
CHARGES_EACH_OWN = 14
|
|
||||||
CHARGES_PAYER = 15
|
|
||||||
|
|
||||||
# values per section 2.8.5 "PAI, Payment Instructions" of
|
|
||||||
# "HSBC - CRG Paymul Message Implementation Guide"
|
|
||||||
MEANS_ACH_OR_EZONE = 2
|
|
||||||
MEANS_PRIORITY_PAYMENT = 52
|
|
||||||
MEANS_FASTER_PAYMENT = 'FPS'
|
|
||||||
|
|
||||||
CHANNEL_INTRA_COMPANY = 'Z24'
|
|
||||||
|
|
||||||
|
|
||||||
class Transaction(LogicalSection, HasCurrency):
|
|
||||||
def _get_amount(self):
|
|
||||||
return self._amount
|
|
||||||
|
|
||||||
def _set_amount(self, amount):
|
|
||||||
if len(str(amount)) > 18:
|
|
||||||
raise ValueError("Amount must be shorter than 18 bytes: %s" %
|
|
||||||
ustr(amount))
|
|
||||||
|
|
||||||
self._amount = amount
|
|
||||||
|
|
||||||
amount = property(_get_amount, _set_amount)
|
|
||||||
|
|
||||||
def _get_payment_reference(self):
|
|
||||||
return self._payment_reference
|
|
||||||
|
|
||||||
def _set_payment_reference(self, payment_reference):
|
|
||||||
if not len(payment_reference) <= 18:
|
|
||||||
raise ValueError(
|
|
||||||
"Payment reference must be <= 18 characters long: %s" %
|
|
||||||
ustr(payment_reference)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not edifact_isalnum(payment_reference):
|
|
||||||
raise ValueError("Payment reference must be alphanumeric: %s" %
|
|
||||||
ustr(payment_reference))
|
|
||||||
|
|
||||||
self._payment_reference = payment_reference.upper()
|
|
||||||
|
|
||||||
payment_reference = property(
|
|
||||||
_get_payment_reference, _set_payment_reference
|
|
||||||
)
|
|
||||||
|
|
||||||
def _get_customer_reference(self):
|
|
||||||
return self._customer_reference
|
|
||||||
|
|
||||||
def _set_customer_reference(self, customer_reference):
|
|
||||||
if not len(customer_reference) <= 18:
|
|
||||||
raise ValueError(
|
|
||||||
"Customer reference must be <= 18 characters long: %s" %
|
|
||||||
ustr(customer_reference)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not edifact_isalnum(customer_reference):
|
|
||||||
raise ValueError("Customer reference must be alphanumeric: %s" %
|
|
||||||
ustr(customer_reference))
|
|
||||||
|
|
||||||
self._customer_reference = customer_reference.upper()
|
|
||||||
|
|
||||||
customer_reference = property(
|
|
||||||
_get_customer_reference,
|
|
||||||
_set_customer_reference
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, amount, currency, account, means,
|
|
||||||
name_address=None, party_name=None, channel='',
|
|
||||||
charges=CHARGES_EACH_OWN, customer_reference=None,
|
|
||||||
payment_reference=None):
|
|
||||||
self.amount = amount
|
|
||||||
self.currency = currency
|
|
||||||
self.account = account
|
|
||||||
self.name_address = name_address
|
|
||||||
self.party_name = party_name
|
|
||||||
self.means = means
|
|
||||||
self.channel = channel
|
|
||||||
self.charges = charges
|
|
||||||
self.payment_reference = payment_reference
|
|
||||||
self.customer_reference = customer_reference
|
|
||||||
|
|
||||||
def segments(self, index):
|
|
||||||
segments = []
|
|
||||||
segments.append([
|
|
||||||
['SEQ'],
|
|
||||||
[''],
|
|
||||||
[index],
|
|
||||||
])
|
|
||||||
segments.append([
|
|
||||||
['MOA'],
|
|
||||||
[9, self.amount.quantize(Decimal('0.00')), self.currency],
|
|
||||||
])
|
|
||||||
|
|
||||||
if self.customer_reference:
|
|
||||||
segments.append([
|
|
||||||
['RFF'],
|
|
||||||
['CR', self.customer_reference],
|
|
||||||
])
|
|
||||||
|
|
||||||
if self.payment_reference:
|
|
||||||
segments.append([
|
|
||||||
['RFF'],
|
|
||||||
['PQ', self.payment_reference],
|
|
||||||
])
|
|
||||||
|
|
||||||
if self.channel:
|
|
||||||
segments.append([
|
|
||||||
['PAI'],
|
|
||||||
['', '', self.means, '', '', self.channel],
|
|
||||||
])
|
|
||||||
else:
|
|
||||||
segments.append([
|
|
||||||
['PAI'],
|
|
||||||
['', '', self.means],
|
|
||||||
])
|
|
||||||
|
|
||||||
segments.append([
|
|
||||||
['FCA'],
|
|
||||||
[self.charges],
|
|
||||||
])
|
|
||||||
|
|
||||||
segments.append(self.account.fii_bf_segment())
|
|
||||||
|
|
||||||
nad_segment = [
|
|
||||||
['NAD'],
|
|
||||||
['BE'],
|
|
||||||
[''],
|
|
||||||
]
|
|
||||||
if self.name_address:
|
|
||||||
nad_segment.append(address_truncate(self.name_address))
|
|
||||||
else:
|
|
||||||
nad_segment.append('')
|
|
||||||
if self.party_name:
|
|
||||||
nad_segment.append(address_truncate(self.party_name))
|
|
||||||
segments.append(nad_segment)
|
|
||||||
|
|
||||||
return segments
|
|
||||||
@@ -1,300 +0,0 @@
|
|||||||
# -*- 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 datetime
|
|
||||||
import unittest2 as unittest
|
|
||||||
import paymul
|
|
||||||
|
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
|
|
||||||
class PaymulTestCase(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.maxDiff = None
|
|
||||||
|
|
||||||
def test_uk_high_value_priority_payment(self):
|
|
||||||
# Changes from spec example: Removed DTM for transaction, HSBC ignores
|
|
||||||
# it (section 2.8.3)
|
|
||||||
expected = """\
|
|
||||||
UNB+UNOA:3+::ABC00000001+::HEXAGON ABC+041111:1500+UKHIGHVALUE'
|
|
||||||
UNH+1+PAYMUL:D:96A:UN:FUN01G'
|
|
||||||
BGM+452+UKHIGHVALUE+9'
|
|
||||||
DTM+137:20041111:102'
|
|
||||||
LIN+1'
|
|
||||||
DTM+203:20041112:102'
|
|
||||||
RFF+AEK:UKHIGHVALUE'
|
|
||||||
MOA+9:1.00:GBP'
|
|
||||||
FII+OR+12345678:HSBC NET TEST::GBP+:::400515:154:133+GB'
|
|
||||||
NAD+OY++HSBC BANK PLC:HSBC NET TEST:TEST:TEST:UNITED KINGDOM'
|
|
||||||
SEQ++1'
|
|
||||||
MOA+9:1.00:GBP'
|
|
||||||
RFF+CR:CRUKHV5'
|
|
||||||
RFF+PQ:PQUKHV5'
|
|
||||||
PAI+::52:::Z24'
|
|
||||||
FCA+13'
|
|
||||||
FII+BF+87654321:XYX LTD FROM FII BF 1:BEN NAME 2:GBP+:::403124:154:133+GB'
|
|
||||||
NAD+BE++SOME BANK PLC:HSBC NET TEST:TEST:TEST:UNITED KINGDOM'
|
|
||||||
CNT+39:1'
|
|
||||||
UNT+19+1'
|
|
||||||
UNZ+1+UKHIGHVALUE'"""
|
|
||||||
|
|
||||||
src_account = paymul.UKAccount(
|
|
||||||
number=12345678,
|
|
||||||
holder='HSBC NET TEST',
|
|
||||||
currency='GBP',
|
|
||||||
sortcode=400515
|
|
||||||
)
|
|
||||||
|
|
||||||
dest_account = paymul.UKAccount(
|
|
||||||
number=87654321,
|
|
||||||
holder="XYX LTD FROM FII BF 1\nBEN NAME 2",
|
|
||||||
currency='GBP',
|
|
||||||
sortcode=403124
|
|
||||||
)
|
|
||||||
|
|
||||||
transaction = paymul.Transaction(
|
|
||||||
amount=Decimal('1.00'),
|
|
||||||
currency='GBP',
|
|
||||||
account=dest_account,
|
|
||||||
charges=paymul.CHARGES_PAYEE,
|
|
||||||
means=paymul.MEANS_PRIORITY_PAYMENT,
|
|
||||||
channel=paymul.CHANNEL_INTRA_COMPANY,
|
|
||||||
name_address="SOME BANK PLC\n"
|
|
||||||
"HSBC NET TEST\n"
|
|
||||||
"TEST\n"
|
|
||||||
"TEST\n"
|
|
||||||
"UNITED KINGDOM",
|
|
||||||
customer_reference='CRUKHV5',
|
|
||||||
payment_reference='PQUKHV5'
|
|
||||||
)
|
|
||||||
|
|
||||||
batch = paymul.Batch(
|
|
||||||
exec_date=datetime.date(2004, 11, 12),
|
|
||||||
reference='UKHIGHVALUE',
|
|
||||||
debit_account=src_account,
|
|
||||||
name_address="HSBC BANK PLC\n"
|
|
||||||
"HSBC NET TEST\n"
|
|
||||||
"TEST\n"
|
|
||||||
"TEST\n"
|
|
||||||
"UNITED KINGDOM")
|
|
||||||
batch.transactions.append(transaction)
|
|
||||||
|
|
||||||
message = paymul.Message(reference='UKHIGHVALUE',
|
|
||||||
dt=datetime.datetime(2004, 11, 11))
|
|
||||||
message.batches.append(batch)
|
|
||||||
|
|
||||||
interchange = paymul.Interchange(
|
|
||||||
client_id='ABC00000001',
|
|
||||||
reference='UKHIGHVALUE',
|
|
||||||
create_dt=datetime.datetime(2004, 11, 11, 15, 00),
|
|
||||||
message=message
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertMultiLineEqual(expected, str(interchange))
|
|
||||||
|
|
||||||
def test_ezone(self):
|
|
||||||
# Changes from example in spec: Changed CNT from 27 to 39, because we
|
|
||||||
# only generate that and it makes no difference which one we use
|
|
||||||
# Removed DTM for transaction, HSBC ignores it (section 2.8.3)
|
|
||||||
|
|
||||||
expected = """UNB+UNOA:3+::ABC12016001+::HEXAGON ABC+080110:0856+EZONE'
|
|
||||||
UNH+1+PAYMUL:D:96A:UN:FUN01G'
|
|
||||||
BGM+452+EZONE+9'
|
|
||||||
DTM+137:20080110:102'
|
|
||||||
LIN+1'
|
|
||||||
DTM+203:20080114:102'
|
|
||||||
RFF+AEK:EZONE'
|
|
||||||
MOA+9:1.00:EUR'
|
|
||||||
FII+OR+12345678:ACCOUNT HOLDER NAME::EUR+:::403124:154:133+GB'
|
|
||||||
NAD+OY++ORD PARTY NAME NADOY 01:CRG TC5 001 NADOY ADDRESS LINE 0001:CRG TC5 \
|
|
||||||
1001 NADOY ADDRESS LINE 0002'
|
|
||||||
SEQ++1'
|
|
||||||
MOA+9:1.00:EUR'
|
|
||||||
RFF+CR:EZONE 1A'
|
|
||||||
RFF+PQ:EZONE 1A'
|
|
||||||
PAI+::2'
|
|
||||||
FCA+14'
|
|
||||||
FII+BF+DE23300308800099990031:CRG TC5 001 BENE NAME FIIBF \
|
|
||||||
000001::EUR+AACSDE33:25:5:::+DE'
|
|
||||||
NAD+BE+++BENE NAME NADBE T1 001:CRG TC5 001T1 NADBE ADD LINE 1 0001:CRG TC5 \
|
|
||||||
001T1 NADBE ADD LINE 2 0001'
|
|
||||||
CNT+39:1'
|
|
||||||
UNT+19+1'
|
|
||||||
UNZ+1+EZONE'"""
|
|
||||||
|
|
||||||
src_account = paymul.UKAccount(
|
|
||||||
number=12345678,
|
|
||||||
holder='ACCOUNT HOLDER NAME',
|
|
||||||
currency='EUR',
|
|
||||||
sortcode=403124
|
|
||||||
)
|
|
||||||
|
|
||||||
dest_account = paymul.IBANAccount(
|
|
||||||
iban="DE23300308800099990031",
|
|
||||||
holder="CRG TC5 001 BENE NAME FIIBF 000001",
|
|
||||||
currency='EUR',
|
|
||||||
bic="AACSDE33"
|
|
||||||
)
|
|
||||||
|
|
||||||
party_name = ("BENE NAME NADBE T1 001\n"
|
|
||||||
"CRG TC5 001T1 NADBE ADD LINE 1 0001\n"
|
|
||||||
"CRG TC5 001T1 NADBE ADD LINE 2 0001")
|
|
||||||
transaction = paymul.Transaction(amount=Decimal('1.00'),
|
|
||||||
currency='EUR',
|
|
||||||
account=dest_account,
|
|
||||||
party_name=party_name,
|
|
||||||
charges=paymul.CHARGES_EACH_OWN,
|
|
||||||
means=paymul.MEANS_EZONE,
|
|
||||||
customer_reference='EZONE 1A',
|
|
||||||
payment_reference='EZONE 1A')
|
|
||||||
|
|
||||||
name_address = ("ORD PARTY NAME NADOY 01\n"
|
|
||||||
"CRG TC5 001 NADOY ADDRESS LINE 0001\n"
|
|
||||||
"CRG TC5 001 NADOY ADDRESS LINE 0002")
|
|
||||||
batch = paymul.Batch(exec_date=datetime.date(2008, 1, 14),
|
|
||||||
reference='EZONE',
|
|
||||||
debit_account=src_account,
|
|
||||||
name_address=name_address)
|
|
||||||
batch.transactions.append(transaction)
|
|
||||||
|
|
||||||
message = paymul.Message(reference='EZONE',
|
|
||||||
dt=datetime.datetime(2008, 1, 10))
|
|
||||||
message.batches.append(batch)
|
|
||||||
|
|
||||||
interchange = paymul.Interchange(
|
|
||||||
client_id='ABC12016001',
|
|
||||||
reference='EZONE',
|
|
||||||
create_dt=datetime.datetime(2008, 1, 10, 8, 56),
|
|
||||||
message=message
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertMultiLineEqual(expected, str(interchange))
|
|
||||||
|
|
||||||
def test_uk_low_value_ach_instruction_level(self):
|
|
||||||
dest_account1 = paymul.UKAccount(
|
|
||||||
number=87654321,
|
|
||||||
holder="HSBC NET RPS TEST\nHSBC BANK",
|
|
||||||
currency='GBP',
|
|
||||||
sortcode=403124
|
|
||||||
)
|
|
||||||
name_address = ("HSBC BANK PLC\n"
|
|
||||||
"PCM\n"
|
|
||||||
"8CS37\n"
|
|
||||||
"E14 5HQ\n"
|
|
||||||
"UNITED KINGDOM")
|
|
||||||
transaction1 = paymul.Transaction(amount=Decimal('1.00'),
|
|
||||||
currency='GBP',
|
|
||||||
account=dest_account1,
|
|
||||||
name_address=name_address,
|
|
||||||
charges=paymul.CHARGES_PAYEE,
|
|
||||||
means=paymul.MEANS_ACH,
|
|
||||||
customer_reference='CREDIT',
|
|
||||||
payment_reference='CREDIT')
|
|
||||||
|
|
||||||
dest_account2 = paymul.UKAccount(
|
|
||||||
number=12341234,
|
|
||||||
holder="HSBC NET RPS TEST\nHSBC BANK",
|
|
||||||
currency='GBP',
|
|
||||||
sortcode=403124
|
|
||||||
)
|
|
||||||
name_address = ("HSBC BANK PLC\n"
|
|
||||||
"PCM\n"
|
|
||||||
"8CS37\n"
|
|
||||||
"E14 5HQ\n"
|
|
||||||
"UNITED KINGDOM")
|
|
||||||
transaction2 = paymul.Transaction(amount=Decimal('1.00'),
|
|
||||||
currency='GBP',
|
|
||||||
account=dest_account2,
|
|
||||||
name_address=name_address,
|
|
||||||
charges=paymul.CHARGES_PAYEE,
|
|
||||||
means=paymul.MEANS_ACH,
|
|
||||||
customer_reference='CREDIT1',
|
|
||||||
payment_reference='CREDIT1')
|
|
||||||
|
|
||||||
name_address = ("HSBC BANK PLC\n"
|
|
||||||
"PCM\n"
|
|
||||||
"8CS37\n"
|
|
||||||
"E14 5HQ\n"
|
|
||||||
"UNITED KINGDOM")
|
|
||||||
|
|
||||||
src_account = paymul.UKAccount(number=12345678,
|
|
||||||
holder='BHEX RPS TEST',
|
|
||||||
currency='GBP',
|
|
||||||
sortcode=401234)
|
|
||||||
batch = paymul.Batch(exec_date=datetime.date(2004, 11, 15),
|
|
||||||
reference='UKLVPLIL',
|
|
||||||
debit_account=src_account,
|
|
||||||
name_address=name_address)
|
|
||||||
batch.transactions = [transaction1, transaction2]
|
|
||||||
|
|
||||||
message = paymul.Message(
|
|
||||||
reference='UKLVPLIL',
|
|
||||||
dt=datetime.datetime(2004, 11, 11)
|
|
||||||
)
|
|
||||||
message.batches.append(batch)
|
|
||||||
|
|
||||||
interchange = paymul.Interchange(
|
|
||||||
client_id='ABC00000001',
|
|
||||||
reference='UKLVPLIL',
|
|
||||||
create_dt=datetime.datetime(2004, 11, 11, 15, 0),
|
|
||||||
message=message
|
|
||||||
)
|
|
||||||
|
|
||||||
# Changes from example:
|
|
||||||
# * Change second transaction from EUR to GBP, because we don't support
|
|
||||||
# multi-currency batches
|
|
||||||
# * Removed DTM for transaction, HSBC ignores it (section 2.8.3)
|
|
||||||
expected = """\
|
|
||||||
UNB+UNOA:3+::ABC00000001+::HEXAGON ABC+041111:1500+UKLVPLIL'
|
|
||||||
UNH+1+PAYMUL:D:96A:UN:FUN01G'
|
|
||||||
BGM+452+UKLVPLIL+9'
|
|
||||||
DTM+137:20041111:102'
|
|
||||||
LIN+1'
|
|
||||||
DTM+203:20041115:102'
|
|
||||||
RFF+AEK:UKLVPLIL'
|
|
||||||
MOA+9:2.00:GBP'
|
|
||||||
FII+OR+12345678:BHEX RPS TEST::GBP+:::401234:154:133+GB'
|
|
||||||
NAD+OY++HSBC BANK PLC:PCM:8CS37:E14 5HQ:UNITED KINGDOM'
|
|
||||||
SEQ++1'
|
|
||||||
MOA+9:1.00:GBP'
|
|
||||||
RFF+CR:CREDIT'
|
|
||||||
RFF+PQ:CREDIT'
|
|
||||||
PAI+::2'
|
|
||||||
FCA+13'
|
|
||||||
FII+BF+87654321:HSBC NET RPS TEST:HSBC BANK:GBP+:::403124:154:133+GB'
|
|
||||||
NAD+BE++HSBC BANK PLC:PCM:8CS37:E14 5HQ:UNITED KINGDOM'
|
|
||||||
SEQ++2'
|
|
||||||
MOA+9:1.00:GBP'
|
|
||||||
RFF+CR:CREDIT1'
|
|
||||||
RFF+PQ:CREDIT1'
|
|
||||||
PAI+::2'
|
|
||||||
FCA+13'
|
|
||||||
FII+BF+12341234:HSBC NET RPS TEST:HSBC BANK:GBP+:::403124:154:133+GB'
|
|
||||||
NAD+BE++HSBC BANK PLC:PCM:8CS37:E14 5HQ:UNITED KINGDOM'
|
|
||||||
CNT+39:2'
|
|
||||||
UNT+27+1'
|
|
||||||
UNZ+1+UKLVPLIL'"""
|
|
||||||
|
|
||||||
self.assertMultiLineEqual(expected, str(interchange))
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
unittest.main()
|
|
||||||
Reference in New Issue
Block a user