Files
bank-payment/account_banking_uk_hsbc/wizard/export_hsbc.py
2013-04-15 15:59:50 +02:00

402 lines
15 KiB
Python

# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
# Copyright (C) 2011 credativ Ltd (<http://www.credativ.co.uk>).
# All Rights Reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import base64
from datetime import datetime, date
from osv import osv, fields
from tools.translate import _
from decimal import Decimal
import paymul
import string
import random
import logging
def strpdate(arg, format='%Y-%m-%d'):
'''shortcut'''
return datetime.strptime(arg, format).date()
def strfdate(arg, format='%Y-%m-%d'):
'''shortcut'''
return arg.strftime(format)
class banking_export_hsbc_wizard(osv.osv_memory):
_name = 'banking.export.hsbc.wizard'
_description = 'HSBC Export'
_columns = {
'state': fields.selection(
[
('create', 'Create'),
('finish', 'Finish')
],
'State',
readonly=True,
),
'test': fields.boolean(),
'reference': fields.char(
'Reference', size=35,
help=('The bank will use this reference in feedback communication '
'to refer to this run. 35 characters are available.'
),
),
'execution_date_create': fields.date(
'Execution Date',
help=('This is the date the file should be processed by the bank. '
'Don\'t choose a date beyond the nearest date in your '
'payments. The latest allowed date is 30 days from now.\n'
'Please keep in mind that banks only execute on working days '
'and typically use a delay of two days between execution date '
'and effective transfer date.'
),
),
'file_id': fields.many2one(
'banking.export.hsbc',
'hsbc File',
readonly=True
),
'file': fields.related(
'file_id', 'file', type='binary',
readonly=True,
string='File',
),
'execution_date_finish': fields.related(
'file_id', 'execution_date', type='date',
readonly=True,
string='Execution Date',
),
'total_amount': fields.related(
'file_id', 'total_amount',
type='float',
string='Total Amount',
readonly=True,
),
'no_transactions': fields.integer(
'Number of Transactions',
readonly=True,
),
'payment_order_ids': fields.many2many(
'payment.order', 'rel_wiz_payorders', 'wizard_id',
'payment_order_id', 'Payment Orders',
readonly=True,
),
}
logger = logging.getLogger('export_hsbc')
def create(self, cursor, uid, wizard_data, context=None):
'''
Retrieve a sane set of default values based on the payment orders
from the context.
'''
if not 'execution_date_create' in wizard_data:
po_ids = context.get('active_ids', [])
po_model = self.pool.get('payment.order')
pos = po_model.browse(cursor, uid, po_ids)
execution_date = date.today()
for po in pos:
if po.date_prefered == 'fixed' and po.date_planned:
execution_date = strpdate(po.date_planned)
elif po.date_prefered == 'due':
for line in po.line_ids:
if line.move_line_id.date_maturity:
date_maturity = strpdate(line.move_line_id.date_maturity)
if date_maturity < execution_date:
execution_date = date_maturity
execution_date = max(execution_date, date.today())
# The default reference contains a /, which is invalid for PAYMUL
reference = pos[0].reference.replace('/', ' ')
wizard_data.update({
'execution_date_create': strfdate(execution_date),
'reference': reference,
'payment_order_ids': [(6, 0, po_ids)],
'state': 'create',
})
return super(banking_export_hsbc_wizard, self).create(
cursor, uid, wizard_data, context)
def _create_account(self, oe_account, origin_country=None, is_origin_account=False):
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.info('Create account %s' % (holder))
self.logger.info('-- %s' % (oe_account.country_id.code))
self.logger.info('-- %s' % (oe_account.acc_number))
if oe_account.state == 'iban':
self.logger.info('IBAN: %s' % (oe_account.acc_number))
paymul_account = paymul.IBANAccount(
iban=oe_account.acc_number,
bic=oe_account.bank.bic,
holder=holder,
currency=currency,
)
transaction_kwargs = {
'charges': paymul.CHARGES_EACH_OWN,
}
elif oe_account.country_id.code == 'GB':
self.logger.info('GB: %s %s' % (oe_account.country_id.code,oe_account.acc_number))
split = oe_account.acc_number.split(" ", 2)
if len(split) == 2:
sortcode, accountno = split
else:
raise osv.except_osv(
_('Error'),
"Invalid GB acccount number '%s'" % oe_account.acc_number)
paymul_account = paymul.UKAccount(
number=accountno,
sortcode=sortcode,
holder=holder,
currency=currency,
)
transaction_kwargs = {
'charges': paymul.CHARGES_PAYEE,
}
elif oe_account.country_id.code in ('US','CA'):
self.logger.info('US/CA: %s %s' % (oe_account.country_id.code,oe_account.acc_number))
split = oe_account.acc_number.split(' ', 2)
if len(split) == 2:
sortcode, accountno = split
else:
raise 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,
is_origin_account=is_origin_account
)
transaction_kwargs = {
'charges': paymul.CHARGES_PAYEE,
}
transaction_kwargs = {
'charges': paymul.CHARGES_PAYEE,
}
else:
self.logger.info('SWIFT Account: %s' % (oe_account.country_id.code))
split = oe_account.acc_number.split(' ', 2)
if len(split) == 2:
sortcode, accountno = split
else:
raise 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
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(
_('Error'),
_('There is insufficient information.\r\n'
'Both destination address and account '
'number must be provided'
)
)
self.logger.info('====')
dest_account, transaction_kwargs = self._create_account(line.bank_id, line.order_id.mode.bank_id.country_id.code)
means = {'ACH or EZONE': paymul.MEANS_ACH_OR_EZONE,
'Faster Payment': paymul.MEANS_FASTER_PAYMENT,
'Priority Payment': paymul.MEANS_PRIORITY_PAYMENT}.get(line.order_id.mode.type.name)
if means is None:
raise 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)),
currency=line.currency.name,
account=dest_account,
means=means,
name_address=line.info_partner,
customer_reference=line.name,
payment_reference=line.name,
**transaction_kwargs
)
except ValueError as exc:
raise osv.except_osv(
_('Error'),
_('Transaction invalid: ') + str(exc)
)
def wizard_export(self, cursor, uid, wizard_data_ids, context):
'''
Wizard to actually create the HSBC file
'''
wizard_data = self.browse(cursor, uid, wizard_data_ids, context)[0]
result_model = self.pool.get('banking.export.hsbc')
payment_orders = wizard_data.payment_order_ids
try:
self.logger.info('Source - %s (%s) %s' % (payment_orders[0].mode.bank_id.partner_id.name, payment_orders[0].mode.bank_id.acc_number, payment_orders[0].mode.bank_id.country_id.code))
src_account = self._create_account(
payment_orders[0].mode.bank_id, payment_orders[0].mode.bank_id.country_id.code, is_origin_account=True
)[0]
except ValueError as exc:
raise osv.except_osv(
_('Error'),
_('Source account invalid: ') + str(exc)
)
if not isinstance(src_account, paymul.UKAccount):
raise osv.except_osv(
_('Error'),
_("Your company's bank account has to have a valid UK "
"account number (not IBAN)" + str(type(src_account)))
)
try:
self.logger.info('Create transactions...')
transactions = []
hsbc_clientid = ''
for po in payment_orders:
transactions += [self._create_transaction(l) for l in po.line_ids]
hsbc_clientid = po.hsbc_clientid_id.clientid
batch = paymul.Batch(
exec_date=strpdate(wizard_data.execution_date_create),
reference=wizard_data.reference,
debit_account=src_account,
name_address=payment_orders[0].line_ids[0].info_owner,
)
batch.transactions = transactions
except ValueError as exc:
raise osv.except_osv(
_('Error'),
_('Batch invalid: ') + str(exc)
)
# Generate random identifier until an unused one is found
while True:
ref = ''.join(random.choice(string.ascii_uppercase + string.digits)
for x in range(15))
ids = result_model.search(cursor, uid, [
('identification', '=', ref)
])
if not ids:
break
message = paymul.Message(reference=ref)
message.batches.append(batch)
interchange = paymul.Interchange(client_id=hsbc_clientid,
reference=ref,
message=message)
export_result = {
'identification': interchange.reference,
'execution_date': batch.exec_date,
'total_amount': batch.amount(),
'no_transactions': len(batch.transactions),
'file': base64.encodestring(str(interchange)),
'payment_order_ids': [
[6, 0, [po.id for po in payment_orders]]
],
}
file_id = result_model.create(cursor, uid, export_result, context)
self.write(cursor, uid, [wizard_data_ids[0]], {
'file_id': file_id,
'no_transactions' : len(batch.transactions),
'state': 'finish',
}, context)
return {
'name': _('HSBC Export'),
'view_type': 'form',
'view_mode': 'form',
'res_model': self._name,
'domain': [],
'context': dict(context, active_ids=wizard_data_ids),
'type': 'ir.actions.act_window',
'target': 'new',
'res_id': wizard_data_ids[0] or False,
}
def wizard_cancel(self, cursor, uid, ids, context):
'''
Cancel the export: just drop the file
'''
wizard_data = self.browse(cursor, uid, ids, context)[0]
result_model = self.pool.get('banking.export.hsbc')
try:
result_model.unlink(cursor, uid, wizard_data.file_id.id)
except AttributeError:
# file_id missing, wizard storage gone, server was restarted
pass
return {'type': 'ir.actions.act_window_close'}
def wizard_save(self, cursor, uid, ids, context):
'''
Save the export: mark all payments in the file as 'sent'
'''
wizard_data = self.browse(cursor, uid, ids, context)[0]
result_model = self.pool.get('banking.export.hsbc')
po_model = self.pool.get('payment.order')
result_model.write(cursor, uid, [wizard_data.file_id.id],
{'state':'sent'})
po_ids = [po.id for po in wizard_data.payment_order_ids]
po_model.action_sent(cursor, uid, po_ids)
return {'type': 'ir.actions.act_window_close'}
banking_export_hsbc_wizard()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: