PEP8 on account_banking_uk_hsbc

This commit is contained in:
Sandy Carter
2014-08-21 12:33:48 -04:00
parent b6ea50c15f
commit a5bab211ad
9 changed files with 492 additions and 340 deletions

View File

@@ -34,16 +34,19 @@
'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).
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.
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.
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.
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.
''',
Initial release of this module was co-sponsored by Canonical.
''',
'installable': True,
}

View File

@@ -5,8 +5,8 @@
# 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
# 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,
@@ -19,12 +19,14 @@
#
##############################################################################
from osv import osv, fields
from datetime import date
from tools.translate import _
class hsbc_export(osv.osv):
'''HSBC Export'''
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'
@@ -39,7 +41,7 @@ class hsbc_export(osv.osv):
'identification':
fields.char('Identification', size=15, readonly=True, select=True),
'execution_date':
fields.date('Execution Date',readonly=True),
fields.date('Execution Date', readonly=True),
'no_transactions':
fields.integer('Number of Transactions', readonly=True),
'total_amount':
@@ -57,51 +59,53 @@ class hsbc_export(osv.osv):
}
_defaults = {
'date_generated': lambda *a: date.today().strftime('%Y-%m-%d'),
'state': lambda *a: 'draft',
'date_generated': lambda *a: date.today().strftime(OE_DATEFORMAT),
'state': 'draft',
}
hsbc_export()
class payment_line(osv.osv):
'''
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.
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.
"""
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.
'''
_name = 'payment.line'
_inherit = 'payment.line'
def info_owner(self, cr, uid, ids, name=None, args=None, context=None):
if not ids: return {}
if not ids:
return {}
result = {}
info=''
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
st1 = '' # no street2 in res.partner.bank
zip = owner.zip and owner.zip or ''
city = owner.city and owner.city 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" + st + " " + st1 + "\n" + zip_city + "\n" +cntry
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 {}
if not ids:
return {}
result = {}
info = ''
@@ -110,24 +114,28 @@ class payment_line(osv.osv):
name = partner.owner_name or partner.partner_id.name
st = partner.street and partner.street or ''
st1 = '' #no street2 in res.partner.bank
st1 = '' # no street2 in res.partner.bank
zip = partner.zip and partner.zip or ''
city = partner.city and partner.city 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" + st + " " + st1 + "\n" + zip_city + "\n" +cntry
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.'),
'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.'
),
}
payment_line()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@@ -1,7 +1,8 @@
# -*- encoding: utf-8 -*-
from osv import osv, fields
from openerp.osv import orm, fields
class hsbc_clientid(osv.osv):
class hsbc_clientid(orm.Model):
"""
Record to hold the HSBCNet Client ID for the company.
"""
@@ -11,38 +12,38 @@ class hsbc_clientid(osv.osv):
_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),
}
'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,
}
hsbc_clientid()
'company_id': (
lambda self, cr, uid, c:
self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id),
}
class payment_order(osv.osv):
_name = 'payment.order'
class payment_order(orm.Model):
_inherit = 'payment.order'
_columns = {
'hsbc_clientid_id': fields.many2one('banking.hsbc.clientid', 'HSBC Client ID', required=True),
}
'hsbc_clientid_id': fields.many2one(
'banking.hsbc.clientid',
'HSBC Client ID',
required=True,
),
}
def _default_hsbc_clientid(self, cr, uid, context=None):
company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
clientid_ids = self.pool.get('banking.hsbc.clientid').search(cr, uid, [('company_id','=',company_id)])
if len(clientid_ids)==0:
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,
}
payment_order()
'hsbc_clientid_id': _default_hsbc_clientid,
}

View File

@@ -22,29 +22,32 @@
#
from account_banking.parsers import models
from tools.translate import _
from mt940_parser import HSBCParser
import re
import osv
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'
'execution_date': 'valuedate',
'value_date': 'valuedate',
'local_currency': 'currency',
'transfer_type': 'bookingcode',
'reference': 'custrefno',
'message': 'furtherinfo'
}
type_map = {
@@ -60,13 +63,13 @@ class transaction(models.mem_bank_transaction):
'''
super(transaction, self).__init__(*args, **kwargs)
for key, value in self.mapping.iteritems():
if record.has_key(value):
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:
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
@@ -74,6 +77,7 @@ class transaction(models.mem_bank_transaction):
if not self.is_valid():
logger.info("Invalid: %s", record)
def is_valid(self):
'''
We don't have remote_account so override base
@@ -81,6 +85,7 @@ class transaction(models.mem_bank_transaction):
return (self.execution_date
and self.transferred_amount and True) or False
class statement(models.mem_bank_statement):
'''
Bank statement imported data
@@ -89,35 +94,44 @@ class statement(models.mem_bank_statement):
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)
self.local_account = (
record['sortcode'] + ' ' + record['accnum'].zfill(8)
)
def _statement_number():
self.id = '-'.join([self.id, self.local_account, record['statementnr']])
self.id = '-'.join(
[self.id, self.local_account, record['statementnr']]
)
def _opening_balance():
self.start_balance = record2float(record,'startingbalance')
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,
'20': _transmission_number,
'25': _account_number,
'28': _statement_number,
'28C': _statement_number,
'60F': _opening_balance,
'62F': _closing_balance,
#'64' : _forward_available,
#'62M': _interim_balance,
'61' : _transaction_new,
'86' : _transaction_info,
'61': _transaction_new,
'86': _transaction_info,
}
rectypes.get(record['recordid'], _not_used)()
@@ -128,15 +142,28 @@ class statement(models.mem_bank_statement):
'''
# Additional information for previous transaction
if len(self.transactions) < 1:
logger.info("Received additional information for non existent transaction:")
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 record.has_key(k)])
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 osv.osv.except_osv(_('Import error'),
'Error in import:%s\n\n%s' % (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'
@@ -153,14 +180,19 @@ class parser_hsbc_mt940(models.parser):
# 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]
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]
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:
@@ -168,5 +200,3 @@ class parser_hsbc_mt940(models.parser):
logger.info(records[0])
return result
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@@ -27,21 +27,23 @@ Based on fi_patu's parser
import re
from datetime import datetime
class HSBCParser(object):
def __init__( self ):
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["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})"
recparse["60F"] = (":(?P<recordid>60F):(?P<creditmarker>[CD])"
"(?P<prevstmtdate>\d{6})(?P<currencycode>.{3})"
"(?P<startingbalance>[\d,]{1,15})")
# Transaction
recparse["61"] = """\
@@ -58,24 +60,24 @@ class HSBCParser(object):
""" % (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}))?"
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})"
# 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
@@ -85,25 +87,27 @@ class HSBCParser(object):
if matchobj:
break
if not matchobj:
print " **** failed to match line '%s'" % (line)
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])
matchdict = dict([(k, v) for k, v in matchdict.iteritems() if v])
matchkeys = set(matchdict.keys())
needstrip = set(["transref", "accnum", "statementnr", "custrefno",
needstrip = set([
"transref", "accnum", "statementnr", "custrefno",
"bankref", "furtherinfo", "infoline1", "infoline2", "infoline3",
"infoline4", "infoline5", "startingbalance", "endingbalance"])
"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(',','.'))
matchdict[field] = float(matchdict[field].replace(',', '.'))
# Convert date fields
needdate = set(["prevstmtdate", "valuedate", "bookingdate"])
@@ -111,14 +115,18 @@ class HSBCParser(object):
datestring = matchdict[field]
post_check = False
if len(datestring) == 4 and field=="bookingdate" and matchdict.has_key("valuedate"):
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')
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)
matchdict[field] = matchdict[field].replace(
year=matchdict[field].year-1
)
except ValueError:
matchdict[field] = None
@@ -141,9 +149,11 @@ class HSBCParser(object):
return output
def parse_file(filename):
hsbcfile = open(filename, "r")
p = HSBCParser().parse(hsbcfile.readlines())
with open(filename, "r") as hsbcfile:
HSBCParser().parse(hsbcfile.readlines())
def main():
"""The main function, currently just calls a dummy filename

View File

@@ -6,8 +6,8 @@
# 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
# 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,
@@ -20,4 +20,4 @@
#
##############################################################################
import export_hsbc
from . import export_hsbc

View File

@@ -6,8 +6,8 @@
# 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
# 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,
@@ -22,23 +22,28 @@
import base64
from datetime import datetime, date
from osv import osv, fields
from tools.translate import _
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'''
"""shortcut"""
return datetime.strptime(arg, format).date()
def strfdate(arg, format='%Y-%m-%d'):
'''shortcut'''
"""shortcut"""
return arg.strftime(format)
class banking_export_hsbc_wizard(osv.osv_memory):
class banking_export_hsbc_wizard(orm.TransientModel):
_name = 'banking.export.hsbc.wizard'
_description = 'HSBC Export'
_columns = {
@@ -62,9 +67,9 @@ class banking_export_hsbc_wizard(osv.osv_memory):
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.'
'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(
@@ -107,7 +112,7 @@ class banking_export_hsbc_wizard(osv.osv_memory):
from the context.
'''
if not 'execution_date_create' in wizard_data:
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)
@@ -120,7 +125,9 @@ class banking_export_hsbc_wizard(osv.osv_memory):
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)
date_maturity = strpdate(
line.move_line_id.date_maturity
)
if date_maturity < execution_date:
execution_date = date_maturity
@@ -139,8 +146,10 @@ class banking_export_hsbc_wizard(osv.osv_memory):
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):
currency = None # let the receiving bank select the currency from the batch
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))
@@ -158,12 +167,13 @@ class banking_export_hsbc_wizard(osv.osv_memory):
'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))
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 osv.except_osv(
raise orm.except_orm(
_('Error'),
"Invalid GB acccount number '%s'" % oe_account.acc_number)
paymul_account = paymul.UKAccount(
@@ -175,15 +185,17 @@ class banking_export_hsbc_wizard(osv.osv_memory):
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))
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 osv.except_osv(
raise orm.except_orm(
_('Error'),
"Invalid %s account number '%s'" % (oe_account.country_id.code,oe_account.acc_number))
_("Invalid %s account number '%s'") %
(oe_account.country_id.code, oe_account.acc_number))
paymul_account = paymul.NorthAmericanAccount(
number=accountno,
sortcode=sortcode,
@@ -201,14 +213,15 @@ class banking_export_hsbc_wizard(osv.osv_memory):
'charges': paymul.CHARGES_PAYEE,
}
else:
self.logger.info('SWIFT Account: %s' % (oe_account.country_id.code))
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 osv.except_osv(
raise orm.except_orm(
_('Error'),
"Invalid %s account number '%s'" % (oe_account.country_id.code,oe_account.acc_number))
_("Invalid %s account number '%s'") %
(oe_account.country_id.code, oe_account.acc_number))
paymul_account = paymul.SWIFTAccount(
number=accountno,
sortcode=sortcode,
@@ -229,25 +242,35 @@ class banking_export_hsbc_wizard(osv.osv_memory):
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 osv.except_osv(
raise orm.except_orm(
_('Error'),
_('There is insufficient information.\r\n'
'Both destination address and account '
'number must be provided'
)
'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)
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 osv.except_osv('Error', "Invalid payment type mode for HSBC '%s'" % line.order_id.mode.type.name)
raise orm.except_orm(
_('Error'),
_("Invalid payment type mode for HSBC '%s'")
% line.order_id.mode.type.name
)
if not line.info_partner:
raise osv.except_osv('Error', "No default address for transaction '%s'" % line.name)
raise orm.except_orm(
_('Error'),
_("No default address for transaction '%s'") % line.name
)
try:
return paymul.Transaction(
@@ -261,9 +284,9 @@ class banking_export_hsbc_wizard(osv.osv_memory):
**transaction_kwargs
)
except ValueError as exc:
raise osv.except_osv(
raise orm.except_orm(
_('Error'),
_('Transaction invalid: ') + str(exc)
_('Transaction invalid: %s') + ustr(exc)
)
def wizard_export(self, cursor, uid, wizard_data_ids, context):
@@ -275,23 +298,29 @@ class banking_export_hsbc_wizard(osv.osv_memory):
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))
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
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 osv.except_osv(
raise orm.except_orm(
_('Error'),
_('Source account invalid: ') + str(exc)
_('Source account invalid: ') + ustr(exc)
)
if not isinstance(src_account, paymul.UKAccount):
raise osv.except_osv(
raise orm.except_orm(
_('Error'),
_("Your company's bank account has to have a valid UK "
"account number (not IBAN)" + str(type(src_account)))
"account number (not IBAN)" + ustr(type(src_account)))
)
try:
@@ -299,7 +328,9 @@ class banking_export_hsbc_wizard(osv.osv_memory):
transactions = []
hsbc_clientid = ''
for po in payment_orders:
transactions += [self._create_transaction(l) for l in po.line_ids]
transactions += [
self._create_transaction(l) for l in po.line_ids
]
hsbc_clientid = po.hsbc_clientid_id.clientid
batch = paymul.Batch(
@@ -310,9 +341,9 @@ class banking_export_hsbc_wizard(osv.osv_memory):
)
batch.transactions = transactions
except ValueError as exc:
raise osv.except_osv(
raise orm.except_orm(
_('Error'),
_('Batch invalid: ') + str(exc)
_('Batch invalid: ') + ustr(exc)
)
# Generate random identifier until an unused one is found
@@ -347,7 +378,7 @@ class banking_export_hsbc_wizard(osv.osv_memory):
self.write(cursor, uid, [wizard_data_ids[0]], {
'file_id': file_id,
'no_transactions' : len(batch.transactions),
'no_transactions': len(batch.transactions),
'state': 'finish',
}, context)
@@ -389,13 +420,9 @@ class banking_export_hsbc_wizard(osv.osv_memory):
po_model = self.pool.get('payment.order')
result_model.write(cursor, uid, [wizard_data.file_id.id],
{'state':'sent'})
{'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'}
banking_export_hsbc_wizard()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@@ -5,8 +5,8 @@
# 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
# 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,
@@ -25,8 +25,14 @@ import datetime
import re
import unicodedata
from openerp.tools import ustr
def strip_accents(string):
return unicodedata.normalize('NFKD', unicode(string)).encode('ASCII', 'ignore')
res = unicodedata.normalize('NFKD', ustr(string))
res = res.encode('ASCII', 'ignore')
return res
def split_account_holder(holder):
holder_parts = holder.split("\n")
@@ -38,17 +44,20 @@ def split_account_holder(holder):
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
"""
The standard says alphanumeric characters, but spaces are also allowed
"""
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 = ''
@@ -58,10 +67,12 @@ def edifact_digits(val, digits=None, mindigits=None):
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
@@ -71,12 +82,13 @@ class HasCurrency(object):
self._currency = None
else:
if not len(currency) <= 3:
raise ValueError("Currency must be <= 3 characters long: " +
str(currency))
raise ValueError("Currency must be <= 3 characters long: %s" %
ustr(currency))
if not edifact_isalnum(currency):
raise ValueError("Currency must be alphanumeric: " + str(currency))
raise ValueError("Currency must be alphanumeric: %s" %
ustr(currency))
self._currency = currency.upper()
currency = property(_get_currency, _set_currency)
@@ -88,17 +100,19 @@ class LogicalSection(object):
segments = self.segments()
def format_segment(segment):
return '+'.join([':'.join([str(strip_accents(y)) for y in x]) for x in 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]]
account_identification = [self.number.replace(' ', ''), holder[0]]
if holder[1] or self.currency:
account_identification.append(holder[1])
if self.currency:
if self.currency:
account_identification.append(self.currency)
return [
['FII'],
@@ -127,8 +141,8 @@ class UKAccount(HasCurrency):
def _set_sortcode(self, sortcode):
if not edifact_digits(sortcode, 6):
raise ValueError("Account sort code must be 6 digits long: " +
str(sortcode))
raise ValueError("Account sort code must be 6 digits long: %s" %
ustr(sortcode))
self._sortcode = sortcode
@@ -141,16 +155,20 @@ class UKAccount(HasCurrency):
holder_parts = split_account_holder(holder)
if not len(holder_parts[0]) <= 35:
raise ValueError("Account holder must be <= 35 characters long: " + str(holder_parts[0]))
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: " + str(holder_parts[1]))
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: " + str(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: " + str(holder_parts[1]))
raise ValueError("Second line of account holder must be "
"alphanumeric: %s" % ustr(holder_parts[1]))
self._holder = holder.upper()
@@ -174,7 +192,7 @@ class UKAccount(HasCurrency):
class NorthAmericanAccount(UKAccount):
def _set_account_ident(self):
if self.origin_country in ('US','CA'):
if self.origin_country in ('US', 'CA'):
# Use the routing number
account_ident = ['', '', '', self.sortcode, 155, 114]
else:
@@ -188,9 +206,9 @@ class NorthAmericanAccount(UKAccount):
else:
expected_digits = 9
if not edifact_digits(sortcode, expected_digits):
raise ValueError("Account routing number must be %d digits long: %s" %
(expected_digits, str(sortcode)))
raise ValueError("Account routing number must be %d digits long: "
"%s" % (expected_digits, ustr(sortcode)))
self._sortcode = sortcode
def _get_sortcode(self):
@@ -199,9 +217,10 @@ class NorthAmericanAccount(UKAccount):
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: " +
str(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):
@@ -211,8 +230,7 @@ class NorthAmericanAccount(UKAccount):
def _set_number(self, number):
if not edifact_digits(number, mindigits=1):
raise ValueError("Account number is invalid: " +
str(number))
raise ValueError("Account number is invalid: %s" % ustr(number))
self._number = number
@@ -221,7 +239,8 @@ class NorthAmericanAccount(UKAccount):
number = property(_get_number, _set_number)
def __init__(self, number, holder, currency, sortcode, swiftcode, country, origin_country=None, is_origin_account=False):
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
@@ -248,9 +267,10 @@ class SWIFTAccount(UKAccount):
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: " +
str(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):
@@ -260,8 +280,8 @@ class SWIFTAccount(UKAccount):
def _set_number(self, number):
if not edifact_digits(number, mindigits=1):
raise ValueError("Account number is invalid: " +
str(number))
raise ValueError("Account number is invalid: %s" %
ustr(number))
self._number = number
@@ -270,7 +290,8 @@ class SWIFTAccount(UKAccount):
number = property(_get_number, _set_number)
def __init__(self, number, holder, currency, sortcode, swiftcode, country, origin_country=None, is_origin_account=False):
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
@@ -289,7 +310,7 @@ class IBANAccount(HasCurrency):
def _set_iban(self, iban):
iban_obj = sepa.IBAN(iban)
if not iban_obj.valid:
raise ValueError("IBAN is invalid: " + str(iban))
raise ValueError("IBAN is invalid: %s" % ustr(iban))
self._iban = iban
self.country = iban_obj.countrycode
@@ -302,21 +323,24 @@ class IBANAccount(HasCurrency):
self.bic = bic
self.currency = currency
self.holder = holder
self.institution_identification = [self.bic, 25, 5, '', '', '' ]
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: " + str(reference))
raise ValueError("Reference must be <= 15 characters long: %s" %
ustr(reference))
if not edifact_isalnum(reference):
raise ValueError("Reference must be alphanumeric: " + str(reference))
raise ValueError("Reference must be alphanumeric: %s" %
ustr(reference))
self._reference = reference.upper()
@@ -335,7 +359,8 @@ class Interchange(LogicalSection):
['UNOA', 3],
['', '', self.client_id],
['', '', 'HEXAGON ABC'],
[self.create_dt.strftime('%y%m%d'), self.create_dt.strftime('%H%M')],
[self.create_dt.strftime('%y%m%d'),
self.create_dt.strftime('%H%M')],
[self.reference],
])
segments += self.message.segments()
@@ -353,10 +378,12 @@ class Message(LogicalSection):
def _set_reference(self, reference):
if not len(reference) <= 35:
raise ValueError("Reference must be <= 35 characters long: " + str(reference))
raise ValueError("Reference must be <= 35 characters long: %s" %
ustr(reference))
if not edifact_isalnum(reference):
raise ValueError("Reference must be alphanumeric: " + str(reference))
raise ValueError("Reference must be alphanumeric: %s" %
ustr(reference))
self._reference = reference.upper()
@@ -406,16 +433,19 @@ class Message(LogicalSection):
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: " + str(reference))
raise ValueError("Reference must be <= 18 characters long: %s" %
ustr(reference))
if not edifact_isalnum(reference):
raise ValueError("Reference must be alphanumeric: " + str(reference))
raise ValueError("Reference must be alphanumeric: %s" %
ustr(reference))
self._reference = reference.upper()
@@ -437,7 +467,7 @@ class Batch(LogicalSection):
# Store the payment means
means = None
if len(self.transactions)>0:
if len(self.transactions) > 0:
means = self.transactions[0].means
segments = []
@@ -455,11 +485,12 @@ class Batch(LogicalSection):
['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")
raise ValueError("All transactions in a batch must have the "
"same currency")
segments.append([
['MOA'],
[9, self.amount().quantize(Decimal('0.00')), currencies.pop()],
@@ -487,11 +518,12 @@ class Batch(LogicalSection):
['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],
[9, transaction.amount.quantize(Decimal('0.00')),
transaction.currency],
])
segments.append(self.debit_account.fii_or_segment())
segments.append([
@@ -503,7 +535,7 @@ class Batch(LogicalSection):
use_index = 1
else:
use_index = index + 1
segments += transaction.segments(use_index)
return segments
@@ -518,20 +550,23 @@ 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"
# 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: " + str(amount))
raise ValueError("Amount must be shorter than 18 bytes: %s" %
ustr(amount))
self._amount = amount
@@ -542,28 +577,41 @@ class Transaction(LogicalSection, HasCurrency):
def _set_payment_reference(self, payment_reference):
if not len(payment_reference) <= 18:
raise ValueError("Payment reference must be <= 18 characters long: " + str(payment_reference))
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: " + str(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)
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: " + str(customer_reference))
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: " + str(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)
customer_reference = property(
_get_customer_reference,
_set_customer_reference
)
def __init__(self, amount, currency, account, means,
name_address=None, party_name=None, channel='',
@@ -636,4 +684,3 @@ class Transaction(LogicalSection, HasCurrency):
segments.append(nad_segment)
return segments

View File

@@ -5,8 +5,8 @@
# 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
# 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,
@@ -25,15 +25,17 @@ 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'
# 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'
@@ -55,46 +57,63 @@ CNT+39:1'
UNT+19+1'
UNZ+1+UKHIGHVALUE'"""
src_account = paymul.UKAccount(number=12345678,
holder='HSBC NET TEST',
currency='GBP',
sortcode=400515)
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)
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\nHSBC NET TEST\nTEST\nTEST\nUNITED KINGDOM",
customer_reference='CRUKHV5',
payment_reference='PQUKHV5')
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\nHSBC NET TEST\nTEST\nTEST\nUNITED KINGDOM")
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)
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
# 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'
@@ -106,33 +125,39 @@ 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 001 NADOY ADDRESS LINE 0002'
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'
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
)
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"
)
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"
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,
@@ -142,9 +167,9 @@ UNZ+1+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"
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,
@@ -155,23 +180,27 @@ UNZ+1+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)
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"
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,
@@ -181,15 +210,17 @@ UNZ+1+EZONE'"""
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"
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,
@@ -199,12 +230,11 @@ UNZ+1+EZONE'"""
customer_reference='CREDIT1',
payment_reference='CREDIT1')
name_address = "HSBC BANK PLC\n" \
+ "PCM\n" \
+ "8CS37\n" \
+ "E14 5HQ\n" \
+ "UNITED KINGDOM"
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',
@@ -216,23 +246,25 @@ UNZ+1+EZONE'"""
name_address=name_address)
batch.transactions = [transaction1, transaction2]
message = paymul.Message(reference='UKLVPLIL',
dt=datetime.datetime(2004, 11, 11))
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)
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'
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'
@@ -264,11 +296,5 @@ UNZ+1+UKLVPLIL'"""
self.assertMultiLineEqual(expected, str(interchange))
if __name__ == "__main__":
# I ran this with
# env PYTHONPATH=$HOME/src/canonical/hsbc-banking:$HOME/src/openerp/6.0/server/bin:$HOME/src/openerp/6.0/addons python wizard/paymul_test.py
# is there a better way?
unittest.main()