diff --git a/account_banking_uk_hsbc/__openerp__.py b/account_banking_uk_hsbc/__openerp__.py index 77a9359b9..a8f274120 100644 --- a/account_banking_uk_hsbc/__openerp__.py +++ b/account_banking_uk_hsbc/__openerp__.py @@ -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, } diff --git a/account_banking_uk_hsbc/account_banking_uk_hsbc.py b/account_banking_uk_hsbc/account_banking_uk_hsbc.py index 0989d3e76..340ddf733 100644 --- a/account_banking_uk_hsbc/account_banking_uk_hsbc.py +++ b/account_banking_uk_hsbc/account_banking_uk_hsbc.py @@ -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: diff --git a/account_banking_uk_hsbc/hsbc_clientid.py b/account_banking_uk_hsbc/hsbc_clientid.py index 84acd5c05..2f33cf9c5 100644 --- a/account_banking_uk_hsbc/hsbc_clientid.py +++ b/account_banking_uk_hsbc/hsbc_clientid.py @@ -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, + } diff --git a/account_banking_uk_hsbc/hsbc_mt940.py b/account_banking_uk_hsbc/hsbc_mt940.py index 5016ba1e2..c1d7b7abc 100644 --- a/account_banking_uk_hsbc/hsbc_mt940.py +++ b/account_banking_uk_hsbc/hsbc_mt940.py @@ -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: diff --git a/account_banking_uk_hsbc/mt940_parser.py b/account_banking_uk_hsbc/mt940_parser.py index 4cfd7ffbe..ea18337ad 100644 --- a/account_banking_uk_hsbc/mt940_parser.py +++ b/account_banking_uk_hsbc/mt940_parser.py @@ -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"] = ":(?P20):(?P.{1,16})" - recparse["25"] = ":(?P25):(?P\d{6})(?P\d{1,29})" + recparse["25"] = (":(?P25):(?P\d{6})" + "(?P\d{1,29})") recparse["28"] = ":(?P28C?):(?P.{1,8})" # Opening balance 60F - recparse["60F"] = ":(?P60F):(?P[CD])" \ - + "(?P\d{6})(?P.{3})" \ - + "(?P[\d,]{1,15})" + recparse["60F"] = (":(?P60F):(?P[CD])" + "(?P\d{6})(?P.{3})" + "(?P[\d,]{1,15})") # Transaction recparse["61"] = """\ @@ -58,24 +60,24 @@ class HSBCParser(object): """ % (patterns) # Further info - recparse["86"] = ":(?P86):" \ - + "(?P.{1,80})?" \ - + "(?:\n(?P.{1,80}))?" \ - + "(?:\n(?P.{1,80}))?" \ - + "(?:\n(?P.{1,80}))?" \ - + "(?:\n(?P.{1,80}))?" + recparse["86"] = (":(?P86):" + "(?P.{1,80})?" + "(?:\n(?P.{1,80}))?" + "(?:\n(?P.{1,80}))?" + "(?:\n(?P.{1,80}))?" + "(?:\n(?P.{1,80}))?") - # Forward available balance (64) / Closing balance (62F) / Interim balance (62M) - recparse["64"] = ":(?P64|62[FM]):" \ - + "(?P[CD])" \ - + "(?P\d{6})(?P.{3})" \ - + "(?P[\d,]{1,15})" + # Forward available balance (64) / Closing balance (62F) + # / Interim balance (62M) + recparse["64"] = (":(?P64|62[FM]):" + "(?P[CD])" + "(?P\d{6})(?P.{3})" + "(?P[\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 diff --git a/account_banking_uk_hsbc/wizard/__init__.py b/account_banking_uk_hsbc/wizard/__init__.py index fb44b448a..beb426b86 100644 --- a/account_banking_uk_hsbc/wizard/__init__.py +++ b/account_banking_uk_hsbc/wizard/__init__.py @@ -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 \ No newline at end of file +from . import export_hsbc diff --git a/account_banking_uk_hsbc/wizard/export_hsbc.py b/account_banking_uk_hsbc/wizard/export_hsbc.py index 05bb822fe..ecc4e9902 100644 --- a/account_banking_uk_hsbc/wizard/export_hsbc.py +++ b/account_banking_uk_hsbc/wizard/export_hsbc.py @@ -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: diff --git a/account_banking_uk_hsbc/wizard/paymul.py b/account_banking_uk_hsbc/wizard/paymul.py index 66e8ecf63..09a9afe81 100644 --- a/account_banking_uk_hsbc/wizard/paymul.py +++ b/account_banking_uk_hsbc/wizard/paymul.py @@ -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 - diff --git a/account_banking_uk_hsbc/wizard/paymul_test.py b/account_banking_uk_hsbc/wizard/paymul_test.py index 7be23c3cd..e6225b93c 100644 --- a/account_banking_uk_hsbc/wizard/paymul_test.py +++ b/account_banking_uk_hsbc/wizard/paymul_test.py @@ -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()