Merge branch 'NL66278-8.0_no_import' into 8.0

This commit is contained in:
Stéphane Bidoul
2015-04-01 22:35:46 +02:00
65 changed files with 0 additions and 6737 deletions

View File

@@ -1 +0,0 @@
import camt

View File

@@ -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,Odoo Community Association (OCA)",
'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,
}

View File

@@ -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

View File

@@ -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

View File

@@ -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,Odoo Community Association (OCA)",
'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,
}

View File

@@ -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 ""

View File

@@ -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"

View File

@@ -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 ""

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -1,54 +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,Odoo Community Association (OCA)",
"license": "AGPL-3",
"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': [],
},
}

View File

@@ -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:])

View File

@@ -1,2 +0,0 @@
# -*- coding: utf-8 -*-
import abnamro

View File

@@ -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,
}

View File

@@ -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

View File

@@ -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 ""

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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,Odoo Community Association (OCA)",
'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,
}

View File

@@ -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:

View File

@@ -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 ""

View File

@@ -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 ""

View File

@@ -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"

View File

@@ -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 ""

View File

@@ -1,2 +0,0 @@
# -*- coding: utf-8 -*-
import ing

View File

@@ -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,
}

View File

@@ -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 ""

View File

@@ -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"

View File

@@ -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 ""

View File

@@ -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

View File

@@ -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

View File

@@ -1,49 +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,Odoo Community Association (OCA)",
"license": "AGPL-3",
"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': [],
},
}

View File

@@ -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

View File

@@ -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

View File

@@ -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,Odoo Community Association (OCA)",
'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,
}

View File

@@ -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 ""

View File

@@ -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 ""

View File

@@ -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"

View File

@@ -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 ""

View File

@@ -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:

View File

@@ -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

View File

@@ -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,
}

View File

@@ -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 ""

View File

@@ -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"

View File

@@ -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"

View File

@@ -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 ""

View File

@@ -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

View File

@@ -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:

View File

@@ -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,Odoo Community Association (OCA)",
'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,
}

View File

@@ -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.'
),
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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,
}

View File

@@ -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>

View File

@@ -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

View File

@@ -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 ""

View File

@@ -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()

View File

@@ -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 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_banking_hsbc_clientid banking.hsbc.clientid model_banking_hsbc_clientid account_payment.group_account_payment 1 1 1 1
3 access_banking_export_hsbc banking.export.hsbc model_banking_export_hsbc account_payment.group_account_payment 1 1 1 1

View File

@@ -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

View File

@@ -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'}

View File

@@ -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>

View File

@@ -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

View File

@@ -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()