mirror of
https://github.com/OCA/bank-payment.git
synced 2025-02-02 10:37:31 +02:00
PEP8 on account_banking_uk_hsbc
This commit is contained in:
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user