credativ team mailing list archive
-
credativ team
-
Mailing list archive
-
Message #03511
[Merge] lp:~jamesj/account-banking/us-canada-payments into lp:~credativ/account-banking/upgrade-6.0
James Jesudason has proposed merging lp:~jamesj/account-banking/us-canada-payments into lp:~credativ/account-banking/upgrade-6.0.
Requested reviews:
credativ (credativ)
For more details, see:
https://code.launchpad.net/~jamesj/account-banking/us-canada-payments/+merge/88822
Allows Priority Payments to non-UK bank accounts and includes validation of US and Canada accounts.
Catches more exceptions to display a user-friendly error message instead of the default exception screen.
Files generated by the module have been validated by HSBCNet and are currently undergoing final approval by HSBC.
--
https://code.launchpad.net/~jamesj/account-banking/us-canada-payments/+merge/88822
Your team credativ is requested to review the proposed merge of lp:~jamesj/account-banking/us-canada-payments into lp:~credativ/account-banking/upgrade-6.0.
=== modified file 'account_banking/account_banking_view.xml'
--- account_banking/account_banking_view.xml 2011-07-21 11:30:59 +0000
+++ account_banking/account_banking_view.xml 2012-01-17 08:49:27 +0000
@@ -384,7 +384,7 @@
<field name="type">form</field>
<field name="arch" type="xml">
<field name="bic" position="replace">
- <field name="bic" on_change="onchange_bic(bic, name)"/>
+ <field name="bic" />
</field>
</field>
</record>
=== modified file 'account_banking_uk_hsbc/data/banking_export_hsbc.xml'
--- account_banking_uk_hsbc/data/banking_export_hsbc.xml 2011-10-25 11:51:12 +0000
+++ account_banking_uk_hsbc/data/banking_export_hsbc.xml 2012-01-17 08:49:27 +0000
@@ -17,5 +17,13 @@
<field name="ir_model_id"
ref="account_banking_uk_hsbc.model_banking_export_hsbc_wizard"/>
</record>
+ <record model="payment.mode.type" id="export_priority_payment">
+ <field name="name">Priority Payment</field>
+ <field name="code">not used</field>
+ <field name="suitable_bank_types"
+ eval="[(6,0,[ref('base_iban.bank_iban'),ref('base.bank_normal'),])]" />
+ <field name="ir_model_id"
+ ref="account_banking_uk_hsbc.model_banking_export_hsbc_wizard"/>
+ </record>
</data>
</openerp>
=== modified file 'account_banking_uk_hsbc/wizard/export_hsbc.py'
--- account_banking_uk_hsbc/wizard/export_hsbc.py 2011-10-25 11:51:12 +0000
+++ account_banking_uk_hsbc/wizard/export_hsbc.py 2012-01-17 08:49:27 +0000
@@ -28,6 +28,7 @@
import paymul
import string
import random
+import netsvc
def strpdate(arg, format='%Y-%m-%d'):
'''shortcut'''
@@ -98,6 +99,8 @@
),
}
+ logger = netsvc.Logger()
+
def create(self, cursor, uid, wizard_data, context=None):
'''
Retrieve a sane set of default values based on the payment orders
@@ -139,8 +142,14 @@
def _create_account(self, oe_account):
currency = None # let the receiving bank select the currency from the batch
holder = oe_account.owner_name or oe_account.partner_id.name
+ self.logger.notifyChannel('paymul', netsvc.LOG_INFO,'Create account %s' % (holder))
+ self.logger.notifyChannel('paymul', netsvc.LOG_INFO,'-- %s' % (oe_account.country_id.code))
+ self.logger.notifyChannel('paymul', netsvc.LOG_INFO,'-- %s' % (oe_account.acc_number))
+ self.logger.notifyChannel('paymul', netsvc.LOG_INFO,'-- %s' % (oe_account.iban))
+
if oe_account.iban:
+ self.logger.notifyChannel('paymul', netsvc.LOG_INFO,'IBAN: %s' % (oe_account.iban))
paymul_account = paymul.IBANAccount(
iban=oe_account.iban,
bic=oe_account.bank.bic,
@@ -151,6 +160,7 @@
'charges': paymul.CHARGES_EACH_OWN,
}
elif oe_account.country_id.code == 'GB':
+ self.logger.notifyChannel('paymul', netsvc.LOG_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
@@ -167,11 +177,53 @@
transaction_kwargs = {
'charges': paymul.CHARGES_PAYEE,
}
+ elif oe_account.country_id.code in ('US','CA'):
+ self.logger.notifyChannel('paymul', netsvc.LOG_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(
+ _('Error'),
+ "Invalid %s account number '%s'" % (oe_account.country_id.code,oe_account.acc_number))
+ paymul_account = paymul.NorthAmericanAccount(
+ number=accountno,
+ sortcode=sortcode,
+ holder=holder,
+ currency=currency,
+ swiftcode=oe_account.bank.bic,
+ country=oe_account.country_id.code,
+ #origin_country=origin_country
+ )
+ transaction_kwargs = {
+ 'charges': paymul.CHARGES_PAYEE,
+ }
+ transaction_kwargs = {
+ 'charges': paymul.CHARGES_PAYEE,
+ }
else:
- raise osv.except_osv(
- _('Error'),
- _('%s: only UK accounts and IBAN are supported') % (holder)
+ self.logger.notifyChannel('paymul', netsvc.LOG_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(
+ _('Error'),
+ "Invalid %s account number '%s'" % (oe_account.country_id.code,oe_account.acc_number))
+ paymul_account = paymul.SWIFTAccount(
+ number=accountno,
+ sortcode=sortcode,
+ holder=holder,
+ currency=currency,
+ swiftcode=oe_account.bank.bic,
+ country=oe_account.country_id.code,
)
+ transaction_kwargs = {
+ 'charges': paymul.CHARGES_PAYEE,
+ }
+ transaction_kwargs = {
+ 'charges': paymul.CHARGES_PAYEE,
+ }
return paymul_account, transaction_kwargs
@@ -185,14 +237,19 @@
'number must be provided'
)
)
-
+
+ self.logger.notifyChannel('paymul', netsvc.LOG_INFO, '====')
dest_account, transaction_kwargs = self._create_account(line.bank_id)
means = {'ACH or EZONE': paymul.MEANS_ACH_OR_EZONE,
- 'Faster Payment': paymul.MEANS_FASTER_PAYMENT}.get(line.order_id.mode.type.name)
+ '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)
+ if not line.info_partner:
+ raise osv.except_osv('Error', "No default address for transaction '%s'" % line.name)
+
try:
return paymul.Transaction(
amount=Decimal(str(line.amount_currency)),
@@ -221,6 +278,7 @@
try:
+ self.logger.notifyChannel('paymul', netsvc.LOG_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,
)[0]
@@ -237,11 +295,12 @@
"account number (not IBAN)" + str(type(src_account)))
)
- transactions = []
- for po in payment_orders:
- transactions += [self._create_transaction(l) for l in po.line_ids]
-
try:
+ self.logger.notifyChannel('paymul', netsvc.LOG_INFO, 'Create transactions...')
+ transactions = []
+ for po in payment_orders:
+ transactions += [self._create_transaction(l) for l in po.line_ids]
+
batch = paymul.Batch(
exec_date=strpdate(wizard_data.execution_date_create),
reference=wizard_data.reference,
=== modified file 'account_banking_uk_hsbc/wizard/paymul.py'
--- account_banking_uk_hsbc/wizard/paymul.py 2011-10-25 11:51:12 +0000
+++ account_banking_uk_hsbc/wizard/paymul.py 2012-01-17 08:49:27 +0000
@@ -23,6 +23,10 @@
from decimal import Decimal
import datetime
import re
+import unicodedata
+
+def strip_accents(string):
+ return unicodedata.normalize('NFKD', unicode(string)).encode('ASCII', 'ignore')
def split_account_holder(holder):
holder_parts = holder.split("\n")
@@ -40,13 +44,19 @@
def edifact_isalnum(s):
return bool(re.match(r'^[A-Za-z0-9 ]*$', s))
-def edifact_digits(val, digits, mindigits=None):
+def edifact_digits(val, digits=None, mindigits=None):
+ if digits is None:
+ digits = ''
if mindigits is None:
mindigits = digits
pattern = r'^[0-9]{' + str(mindigits) + ',' + str(digits) + r'}$'
return bool(re.match(pattern, str(val)))
+def edifact_isalnum_size(val, digits):
+ pattern = r'^[A-Za-z0-9 ]{' + str(digits) + ',' + str(digits) + r'}$'
+ return bool(re.match(pattern, str(val)))
+
class HasCurrency(object):
def _get_currency(self):
return self._currency
@@ -73,14 +83,14 @@
segments = self.segments()
def format_segment(segment):
- return '+'.join([':'.join([str(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, holder[0]]
+ account_identification = [self.number.replace(' ',''), holder[0]]
if holder[1] or self.currency:
account_identification.append(holder[1])
if self.currency:
@@ -126,16 +136,16 @@
holder_parts = split_account_holder(holder)
if not len(holder_parts[0]) <= 35:
- raise ValueError("Account holder must be <= 35 characters long")
+ raise ValueError("Account holder must be <= 35 characters long: " + str(holder_parts[0]))
if not len(holder_parts[1]) <= 35:
- raise ValueError("Second line of account holder must be <= 35 characters long")
+ raise ValueError("Second line of account holder must be <= 35 characters long: " + str(holder_parts[1]))
if not edifact_isalnum(holder_parts[0]):
- raise ValueError("Account holder must be alphanumeric")
+ raise ValueError("Account holder must be alphanumeric: " + str(holder_parts[0]))
if not edifact_isalnum(holder_parts[1]):
- raise ValueError("Second line of account holder must be alphanumeric")
+ raise ValueError("Second line of account holder must be alphanumeric: " + str(holder_parts[1]))
self._holder = holder.upper()
@@ -155,6 +165,113 @@
def fii_or_segment(self):
return _fii_segment(self, 'OR')
+
+class NorthAmericanAccount(UKAccount):
+
+ def _set_account_ident(self):
+ if self.origin_country in ('US','CA'):
+ # Use the routing number
+ account_ident = ['', '', '', self.sortcode, 155, 114]
+ else:
+ # Using the BIC/Swift Code
+ account_ident = [self.bic, 25, 5, '', '', '']
+ return account_ident
+
+ def _set_sortcode(self, sortcode):
+ if not edifact_digits(sortcode, 9):
+ raise ValueError("Account routing number must be 9 digits long: " +
+ str(sortcode))
+
+
+ self._sortcode = sortcode
+
+ def _get_sortcode(self):
+ return self._sortcode
+
+ sortcode = property(_get_sortcode, _set_sortcode)
+
+ def _set_bic(self, bic):
+ if not edifact_isalnum_size(bic, 8) and not edifact_isalnum_size(bic, 11):
+ raise ValueError("Account BIC/Swift code must be 8 or 11 characters long: " +
+ str(bic))
+ self._bic = bic
+
+ def _get_bic(self):
+ return self._bic
+
+ bic = property(_get_bic, _set_bic)
+
+ def _set_number(self, number):
+ if not edifact_digits(number, mindigits=1):
+ raise ValueError("Account number is invalid: " +
+ str(number))
+
+ self._number = number
+
+ def _get_number(self):
+ return self._number
+
+ number = property(_get_number, _set_number)
+
+ def __init__(self, number, holder, currency, sortcode, swiftcode, country, origin_country=None):
+ self.number = number
+ self.holder = holder
+ self.currency = currency
+ self.sortcode = sortcode
+ self.country = country
+ self.bic = swiftcode
+ self.origin_country = origin_country
+ self.institution_identification = self._set_account_ident()
+
+
+class SWIFTAccount(UKAccount):
+
+ def _set_account_ident(self):
+ # Using the BIC/Swift Code
+ return [self.bic, 25, 5, '', '', '']
+
+ def _set_sortcode(self, sortcode):
+ self._sortcode = sortcode
+
+ def _get_sortcode(self):
+ return self._sortcode
+
+ sortcode = property(_get_sortcode, _set_sortcode)
+
+ def _set_bic(self, bic):
+ if not edifact_isalnum_size(bic, 8) and not edifact_isalnum_size(bic, 11):
+ raise ValueError("Account BIC/Swift code must be 8 or 11 characters long: " +
+ str(bic))
+ self._bic = bic
+
+ def _get_bic(self):
+ return self._bic
+
+ bic = property(_get_bic, _set_bic)
+
+ def _set_number(self, number):
+ if not edifact_digits(number, mindigits=1):
+ raise ValueError("Account number is invalid: " +
+ str(number))
+
+ self._number = number
+
+ def _get_number(self):
+ return self._number
+
+ number = property(_get_number, _set_number)
+
+ def __init__(self, number, holder, currency, sortcode, swiftcode, country, origin_country=None):
+ self.number = number
+ self.holder = holder
+ self.currency = currency
+ self.sortcode = sortcode
+ self.country = country
+ self.bic = swiftcode
+ self.origin_country = origin_country
+ self.institution_identification = self._set_account_ident()
+
+
class IBANAccount(HasCurrency):
def _get_iban(self):
return self._iban
@@ -162,7 +279,7 @@
def _set_iban(self, iban):
iban_obj = sepa.IBAN(iban)
if not iban_obj.valid:
- raise ValueError("IBAN is invalid")
+ raise ValueError("IBAN is invalid: " + str(iban))
self._iban = iban
self.country = iban_obj.countrycode
@@ -186,10 +303,10 @@
def _set_reference(self, reference):
if not len(reference) <= 15:
- raise ValueError("Reference must be <= 15 characters long")
+ raise ValueError("Reference must be <= 15 characters long: " + str(reference))
if not edifact_isalnum(reference):
- raise ValueError("Reference must be alphanumeric")
+ raise ValueError("Reference must be alphanumeric: " + str(reference))
self._reference = reference.upper()
@@ -226,10 +343,10 @@
def _set_reference(self, reference):
if not len(reference) <= 35:
- raise ValueError("Reference must be <= 35 characters long")
+ raise ValueError("Reference must be <= 35 characters long: " + str(reference))
if not edifact_isalnum(reference):
- raise ValueError("Reference must be alphanumeric")
+ raise ValueError("Reference must be alphanumeric: " + str(reference))
self._reference = reference.upper()
@@ -285,10 +402,10 @@
def _set_reference(self, reference):
if not len(reference) <= 18:
- raise ValueError("Reference must be <= 18 characters long")
+ raise ValueError("Reference must be <= 18 characters long: " + str(reference))
if not edifact_isalnum(reference):
- raise ValueError("Reference must be alphanumeric")
+ raise ValueError("Reference must be alphanumeric: " + str(reference))
self._reference = reference.upper()
@@ -306,41 +423,78 @@
def segments(self, index):
if not edifact_digits(index, 6, 1):
- raise ValueError("Index must be 6 digits or less")
+ raise ValueError("Index must be 6 digits or less: " + str(index))
+
+ # Store the payment means
+ means = None
+ if len(self.transactions)>0:
+ means = self.transactions[0].means
segments = []
- segments.append([
- ['LIN'],
- [index],
- ])
- segments.append([
- ['DTM'],
- [203, self.exec_date.strftime('%Y%m%d'), 102],
- ])
- segments.append([
- ['RFF'],
- ['AEK', self.reference],
- ])
-
- currencies = set([x.currency for x in self.transactions])
- if len(currencies) > 1:
- raise ValueError("All transactions in a batch must have the same currency")
-
- segments.append([
- ['MOA'],
- [9, self.amount().quantize(Decimal('0.00')), currencies.pop()],
- ])
- segments.append(self.debit_account.fii_or_segment())
- segments.append([
- ['NAD'],
- ['OY'],
- [''],
- self.name_address.upper().split("\n")[0:5],
- ])
+ if means != MEANS_PRIORITY_PAYMENT:
+ segments.append([
+ ['LIN'],
+ [index],
+ ])
+ segments.append([
+ ['DTM'],
+ [203, self.exec_date.strftime('%Y%m%d'), 102],
+ ])
+ segments.append([
+ ['RFF'],
+ ['AEK', self.reference],
+ ])
+
+ currencies = set([x.currency for x in self.transactions])
+ if len(currencies) > 1:
+ raise ValueError("All transactions in a batch must have the same currency")
+
+ segments.append([
+ ['MOA'],
+ [9, self.amount().quantize(Decimal('0.00')), currencies.pop()],
+ ])
+ segments.append(self.debit_account.fii_or_segment())
+ segments.append([
+ ['NAD'],
+ ['OY'],
+ [''],
+ self.name_address.upper().split("\n")[0:5],
+ ])
for index, transaction in enumerate(self.transactions):
- segments += transaction.segments(index + 1)
+ if transaction.means == MEANS_PRIORITY_PAYMENT:
+ # Need a debit-credit format for Priority Payments
+ segments.append([
+ ['LIN'],
+ [index+1],
+ ])
+ segments.append([
+ ['DTM'],
+ [203, self.exec_date.strftime('%Y%m%d'), 102],
+ ])
+ segments.append([
+ ['RFF'],
+ ['AEK', self.reference],
+ ])
+
+ # Use the transaction amount and currency for the debit line
+ segments.append([
+ ['MOA'],
+ [9, transaction.amount.quantize(Decimal('0.00')), transaction.currency],
+ ])
+ segments.append(self.debit_account.fii_or_segment())
+ segments.append([
+ ['NAD'],
+ ['OY'],
+ [''],
+ self.name_address.upper().split("\n")[0:5],
+ ])
+ use_index = 1
+ else:
+ use_index = index + 1
+
+ segments += transaction.segments(use_index)
return segments
@@ -367,7 +521,7 @@
def _set_amount(self, amount):
if len(str(amount)) > 18:
- raise ValueError("Amount must be shorter than 18 bytes")
+ raise ValueError("Amount must be shorter than 18 bytes: " + str(amount))
self._amount = amount
@@ -378,10 +532,10 @@
def _set_payment_reference(self, payment_reference):
if not len(payment_reference) <= 18:
- raise ValueError("Payment reference must be <= 18 characters long")
+ raise ValueError("Payment reference must be <= 18 characters long: " + str(payment_reference))
if not edifact_isalnum(payment_reference):
- raise ValueError("Payment reference must be alphanumeric")
+ raise ValueError("Payment reference must be alphanumeric: " + str(payment_reference))
self._payment_reference = payment_reference.upper()
@@ -392,10 +546,10 @@
def _set_customer_reference(self, customer_reference):
if not len(customer_reference) <= 18:
- raise ValueError("Customer reference must be <= 18 characters long")
+ raise ValueError("Customer reference must be <= 18 characters long: " + str(customer_reference))
if not edifact_isalnum(customer_reference):
- raise ValueError("Customer reference must be alphanumeric")
+ raise ValueError("Customer reference must be alphanumeric: " + str(customer_reference))
self._customer_reference = customer_reference.upper()
@@ -472,3 +626,4 @@
segments.append(nad_segment)
return segments
+
Follow ups