mirror of
https://github.com/OCA/bank-payment.git
synced 2025-02-02 10:37:31 +02:00
@@ -36,7 +36,7 @@ class AccountBankStatement(orm.Model):
|
||||
@param defaults: dictionary of default move line values. Usually
|
||||
the same as the originating move line.
|
||||
|
||||
return one or more serialized tax move lines and a set of values to
|
||||
return one or more serialized tax move lines and a set of values to
|
||||
update the originating move line with, containing the new amount.
|
||||
"""
|
||||
|
||||
@@ -50,8 +50,8 @@ class AccountBankStatement(orm.Model):
|
||||
|
||||
fiscal_position = (
|
||||
st_line.partner_id.property_account_position
|
||||
if st_line.partner_id and
|
||||
st_line.partner_id.property_account_position
|
||||
if st_line.partner_id
|
||||
and st_line.partner_id.property_account_position
|
||||
else False)
|
||||
tax_ids = self.pool.get('account.fiscal.position').map_tax(
|
||||
cr, uid, fiscal_position, [st_line.tax_id])
|
||||
@@ -66,25 +66,33 @@ class AccountBankStatement(orm.Model):
|
||||
update_move_line['tax_code_id'] = tax['base_code_id']
|
||||
update_move_line['tax_amount'] = tax['base_sign'] * (
|
||||
computed_taxes.get('total', 0.0))
|
||||
# As the tax is inclusive, we need to correct the amount on the
|
||||
# original move line
|
||||
# As the tax is inclusive, we need to correct the amount
|
||||
# on the original move line
|
||||
amount = computed_taxes.get('total', 0.0)
|
||||
update_move_line['credit'] = ((amount < 0) and -amount) or 0.0
|
||||
update_move_line['debit'] = ((amount > 0) and amount) or 0.0
|
||||
update_move_line['credit'] = (
|
||||
(amount < 0) and -amount) or 0.0
|
||||
update_move_line['debit'] = (
|
||||
(amount > 0) and amount) or 0.0
|
||||
|
||||
move_lines.append({
|
||||
'move_id': defaults['move_id'],
|
||||
'name': defaults.get('name', '') + ' ' + ustr(tax['name'] or ''),
|
||||
'name': (
|
||||
defaults.get('name', '')
|
||||
+ ' ' + ustr(tax['name'] or '')),
|
||||
'date': defaults.get('date', False),
|
||||
'partner_id': defaults.get('partner_id', False),
|
||||
'ref': defaults.get('ref', False),
|
||||
'statement_id': defaults.get('statement_id'),
|
||||
'tax_code_id': tax['tax_code_id'],
|
||||
'tax_amount': tax['tax_sign'] * tax.get('amount', 0.0),
|
||||
'account_id': tax.get('account_collected_id', defaults['account_id']),
|
||||
'account_id': (
|
||||
tax.get('account_collected_id',
|
||||
defaults['account_id'])),
|
||||
'credit': tax['amount'] < 0 and - tax['amount'] or 0.0,
|
||||
'debit': tax['amount'] > 0 and tax['amount'] or 0.0,
|
||||
'account_id': tax.get('account_collected_id', defaults['account_id']),
|
||||
'account_id': (
|
||||
tax.get('account_collected_id',
|
||||
defaults['account_id'])),
|
||||
})
|
||||
|
||||
return move_lines, update_move_line
|
||||
|
||||
@@ -28,7 +28,7 @@ class AccountBankStatementLine(orm.Model):
|
||||
_columns = {
|
||||
'tax_id': fields.many2one(
|
||||
'account.tax', 'Tax',
|
||||
domain=[('price_include','=', True)],
|
||||
domain=[('price_include', '=', True)],
|
||||
help="Apply an (inclusive) tax from the bank statement line",
|
||||
),
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
# garantees and support are strongly adviced to contract EduSense BV
|
||||
#
|
||||
# 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,
|
||||
@@ -24,6 +24,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import sepa
|
||||
import record
|
||||
import banking_import_transaction
|
||||
@@ -33,5 +34,3 @@ import wizard
|
||||
import res_partner
|
||||
import res_bank
|
||||
import res_partner_bank
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -70,7 +70,8 @@
|
||||
+ IBAN accounts are the standard in the SEPA countries
|
||||
+ local accounts are derived from SEPA (excluding Turkey) but are
|
||||
considered to be identical to the corresponding SEPA account.
|
||||
+ Banks are identified with either Country + Bank code + Branch code or BIC
|
||||
+ Banks are identified with either Country + Bank code + Branch code or
|
||||
BIC
|
||||
+ Each bank can have its own pace in introducing SEPA into their
|
||||
communication with their customers.
|
||||
+ National online databases can be used to convert BBAN's to IBAN's.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
||||
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
# All Rights Reserved
|
||||
@@ -86,11 +86,11 @@ class account_banking_account_settings(orm.Model):
|
||||
string='Partner'),
|
||||
'default_credit_account_id': fields.many2one(
|
||||
'account.account', 'Default credit account', select=True,
|
||||
help=('The account to use when an unexpected payment was signaled. '
|
||||
'This can happen when a direct debit payment is cancelled '
|
||||
help=('The account to use when an unexpected payment was signaled.'
|
||||
' This can happen when a direct debit payment is cancelled '
|
||||
'by a customer, or when no matching payment can be found. '
|
||||
' Mind that you can correct movements before confirming them.'
|
||||
),
|
||||
'Mind that you can correct movements before confirming them.'
|
||||
),
|
||||
required=True
|
||||
),
|
||||
'default_debit_account_id': fields.many2one(
|
||||
@@ -98,27 +98,27 @@ class account_banking_account_settings(orm.Model):
|
||||
select=True, required=True,
|
||||
help=('The account to use when an unexpected payment is received. '
|
||||
'This can be needed when a customer pays in advance or when '
|
||||
'no matching invoice can be found. Mind that you can correct '
|
||||
'movements before confirming them.'
|
||||
),
|
||||
'no matching invoice can be found. Mind that you can '
|
||||
'correct movements before confirming them.'
|
||||
),
|
||||
),
|
||||
'costs_account_id': fields.many2one(
|
||||
'account.account', 'Bank Costs Account', select=True,
|
||||
help=('The account to use when the bank invoices its own costs. '
|
||||
'Leave it blank to disable automatic invoice generation '
|
||||
'on bank costs.'
|
||||
),
|
||||
),
|
||||
),
|
||||
'invoice_journal_id': fields.many2one(
|
||||
'account.journal', 'Costs Journal',
|
||||
'account.journal', 'Costs Journal',
|
||||
help=('This is the journal used to create invoices for bank costs.'
|
||||
),
|
||||
),
|
||||
),
|
||||
'bank_partner_id': fields.many2one(
|
||||
'res.partner', 'Bank Partner',
|
||||
help=('The partner to use for bank costs. Banks are not partners '
|
||||
'by default. You will most likely have to create one.'
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
}
|
||||
@@ -134,7 +134,7 @@ class account_banking_account_settings(orm.Model):
|
||||
return user['company_id'][0]
|
||||
return self.pool.get('res.company').search(
|
||||
cr, uid, [('parent_id', '=', False)])[0]
|
||||
|
||||
|
||||
def _default_partner_id(self, cr, uid, context=None, company_id=False):
|
||||
if not company_id:
|
||||
company_id = self._default_company(cr, uid, context=context)
|
||||
@@ -196,7 +196,7 @@ class account_banking_account_settings(orm.Model):
|
||||
values['journal_id'] = bank['journal_id'][0]
|
||||
return {'value': values}
|
||||
|
||||
def onchange_company_id (
|
||||
def onchange_company_id(
|
||||
self, cr, uid, ids, company_id=False, context=None):
|
||||
if not company_id:
|
||||
return {}
|
||||
@@ -229,37 +229,59 @@ class account_banking_imported_file(orm.Model):
|
||||
_description = __doc__
|
||||
_rec_name = 'date'
|
||||
_columns = {
|
||||
'company_id': fields.many2one('res.company', 'Company',
|
||||
select=True, readonly=True
|
||||
),
|
||||
'date': fields.datetime('Import Date', readonly=True, select=True,
|
||||
states={'draft': [('readonly', False)]}
|
||||
),
|
||||
'format': fields.char('File Format', size=20, readonly=True,
|
||||
states={'draft': [('readonly', False)]}
|
||||
),
|
||||
'file': fields.binary('Raw Data', readonly=True,
|
||||
states={'draft': [('readonly', False)]}
|
||||
),
|
||||
'file_name': fields.char('File name', size=256),
|
||||
'log': fields.text('Import Log', readonly=True,
|
||||
states={'draft': [('readonly', False)]}
|
||||
),
|
||||
'user_id': fields.many2one('res.users', 'Responsible User',
|
||||
readonly=True, select=True,
|
||||
states={'draft': [('readonly', False)]}
|
||||
),
|
||||
'state': fields.selection(
|
||||
[('unfinished', 'Unfinished'),
|
||||
('error', 'Error'),
|
||||
('review', 'Review'),
|
||||
('ready', 'Finished'),
|
||||
], 'State', select=True, readonly=True
|
||||
'company_id': fields.many2one(
|
||||
'res.company',
|
||||
'Company',
|
||||
select=True,
|
||||
readonly=True,
|
||||
),
|
||||
'date': fields.datetime(
|
||||
'Import Date',
|
||||
readonly=True,
|
||||
select=True,
|
||||
states={'draft': [('readonly', False)]},
|
||||
),
|
||||
'format': fields.char(
|
||||
'File Format',
|
||||
size=20,
|
||||
readonly=True,
|
||||
states={'draft': [('readonly', False)]},
|
||||
),
|
||||
'file': fields.binary(
|
||||
'Raw Data',
|
||||
readonly=True,
|
||||
states={'draft': [('readonly', False)]},
|
||||
),
|
||||
'file_name': fields.char('File name', size=256),
|
||||
'log': fields.text(
|
||||
'Import Log',
|
||||
readonly=True,
|
||||
states={'draft': [('readonly', False)]},
|
||||
),
|
||||
'user_id': fields.many2one(
|
||||
'res.users',
|
||||
'Responsible User',
|
||||
readonly=True,
|
||||
select=True,
|
||||
states={'draft': [('readonly', False)]},
|
||||
),
|
||||
'state': fields.selection(
|
||||
[
|
||||
('unfinished', 'Unfinished'),
|
||||
('error', 'Error'),
|
||||
('review', 'Review'),
|
||||
('ready', 'Finished'),
|
||||
],
|
||||
'State',
|
||||
select=True,
|
||||
readonly=True,
|
||||
),
|
||||
'statement_ids': fields.one2many(
|
||||
'account.bank.statement',
|
||||
'banking_id',
|
||||
'Statements',
|
||||
readonly=False,
|
||||
),
|
||||
'statement_ids': fields.one2many('account.bank.statement',
|
||||
'banking_id', 'Statements',
|
||||
readonly=False,
|
||||
),
|
||||
}
|
||||
_defaults = {
|
||||
'date': fields.date.context_today,
|
||||
@@ -284,11 +306,17 @@ class account_bank_statement(orm.Model):
|
||||
_inherit = 'account.bank.statement'
|
||||
|
||||
_columns = {
|
||||
'period_id': fields.many2one('account.period', 'Period',
|
||||
required=False, readonly=True),
|
||||
'banking_id': fields.many2one('account.banking.imported.file',
|
||||
'Imported File', readonly=True,
|
||||
),
|
||||
'period_id': fields.many2one(
|
||||
'account.period',
|
||||
'Period',
|
||||
required=False,
|
||||
readonly=True,
|
||||
),
|
||||
'banking_id': fields.many2one(
|
||||
'account.banking.imported.file',
|
||||
'Imported File',
|
||||
readonly=True,
|
||||
),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
@@ -316,12 +344,12 @@ class account_bank_statement(orm.Model):
|
||||
statement.write({'period_id': line.period_id.id})
|
||||
statement.refresh()
|
||||
return True
|
||||
|
||||
|
||||
# Redefine the constraint, or it still refer to the original method
|
||||
_constraints = [
|
||||
(_check_company_id,
|
||||
'The journal and period chosen have to belong to the same company.',
|
||||
['journal_id','period_id']),
|
||||
['journal_id', 'period_id']),
|
||||
]
|
||||
|
||||
def _get_period(self, cr, uid, date=False, context=None):
|
||||
@@ -410,7 +438,7 @@ class account_bank_statement(orm.Model):
|
||||
account_move_obj.post(
|
||||
cr, uid, [st_line.voucher_id.move_id.id], context={})
|
||||
else:
|
||||
# Write stored reconcile_id and pay invoices through workflow
|
||||
# Write stored reconcile_id and pay invoices through workflow
|
||||
if st_line.reconcile_id:
|
||||
move_ids = [move.id for move in st_line.move_ids]
|
||||
torec = account_move_line_obj.search(
|
||||
@@ -419,11 +447,12 @@ class account_bank_statement(orm.Model):
|
||||
('account_id', '=', st_line.account_id.id)],
|
||||
context=context)
|
||||
account_move_line_obj.write(cr, uid, torec, {
|
||||
(st_line.reconcile_id.line_partial_ids and
|
||||
'reconcile_partial_id' or 'reconcile_id'):
|
||||
st_line.reconcile_id.id }, context=context)
|
||||
(st_line.reconcile_id.line_partial_ids
|
||||
and 'reconcile_partial_id'
|
||||
or 'reconcile_id'): st_line.reconcile_id.id
|
||||
}, context=context)
|
||||
for move_line in (st_line.reconcile_id.line_id or []) + (
|
||||
st_line.reconcile_id.line_partial_ids or []):
|
||||
st_line.reconcile_id.line_partial_ids or []):
|
||||
netsvc.LocalService("workflow").trg_trigger(
|
||||
uid, 'account.move.line', move_line.id, cr)
|
||||
return res
|
||||
@@ -438,7 +467,7 @@ class account_bank_statement(orm.Model):
|
||||
if ids and isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
noname_ids = self.search(
|
||||
cr, uid, [('id', 'in', ids),('name', '=', '/')],
|
||||
cr, uid, [('id', 'in', ids), ('name', '=', '/')],
|
||||
context=context)
|
||||
for st in self.browse(cr, uid, noname_ids, context=context):
|
||||
if st.journal_id.sequence_id:
|
||||
@@ -451,7 +480,7 @@ class account_bank_statement(orm.Model):
|
||||
cr, uid, st.journal_id.sequence_id.id, context=c)
|
||||
self.write(
|
||||
cr, uid, ids, {'name': st_number}, context=context)
|
||||
|
||||
|
||||
return super(account_bank_statement, self).button_confirm_bank(
|
||||
cr, uid, ids, context)
|
||||
|
||||
@@ -464,7 +493,7 @@ class account_voucher(orm.Model):
|
||||
context = {}
|
||||
if not context.get('period_id') and context.get('move_line_ids'):
|
||||
move_line = self.pool.get('account.move.line').browse(
|
||||
cr, uid , context.get('move_line_ids')[0], context=context)
|
||||
cr, uid, context.get('move_line_ids')[0], context=context)
|
||||
return move_line.period_id.id
|
||||
return super(account_voucher, self)._get_period(cr, uid, context)
|
||||
|
||||
@@ -498,8 +527,8 @@ class account_bank_statement_line(orm.Model):
|
||||
which is inaccessible from within this method.
|
||||
'''
|
||||
res_users_obj = self.pool.get('res.users')
|
||||
return res_users_obj.browse(cr, uid, uid,
|
||||
context=context).company_id.currency_id.id
|
||||
return res_users_obj.browse(
|
||||
cr, uid, uid, context=context).company_id.currency_id.id
|
||||
|
||||
def _get_invoice_id(self, cr, uid, ids, name, args, context=None):
|
||||
res = {}
|
||||
@@ -509,7 +538,7 @@ class account_bank_statement_line(orm.Model):
|
||||
st_line.reconcile_id and
|
||||
(st_line.reconcile_id.line_id or
|
||||
st_line.reconcile_id.line_partial_ids) or
|
||||
st_line.import_transaction_id and
|
||||
st_line.import_transaction_id and
|
||||
st_line.import_transaction_id.move_line_id and
|
||||
[st_line.import_transaction_id.move_line_id] or []):
|
||||
if move_line.invoice:
|
||||
@@ -519,36 +548,70 @@ class account_bank_statement_line(orm.Model):
|
||||
|
||||
_columns = {
|
||||
# Redefines. Todo: refactor away to view attrs
|
||||
'amount': fields.float('Amount', readonly=True,
|
||||
digits_compute=dp.get_precision('Account'),
|
||||
states={'draft': [('readonly', False)]}),
|
||||
'ref': fields.char('Ref.', size=32, readonly=True,
|
||||
states={'draft': [('readonly', False)]}),
|
||||
'name': fields.char('Name', size=64, required=False, readonly=True,
|
||||
states={'draft': [('readonly', False)]}),
|
||||
'date': fields.date('Date', required=True, readonly=True,
|
||||
states={'draft': [('readonly', False)]}),
|
||||
|
||||
'amount': fields.float(
|
||||
'Amount',
|
||||
readonly=True,
|
||||
digits_compute=dp.get_precision('Account'),
|
||||
states={'draft': [('readonly', False)]},
|
||||
),
|
||||
'ref': fields.char(
|
||||
'Ref.',
|
||||
size=32,
|
||||
readonly=True,
|
||||
states={'draft': [('readonly', False)]},
|
||||
),
|
||||
'name': fields.char(
|
||||
'Name',
|
||||
size=64,
|
||||
required=False,
|
||||
readonly=True,
|
||||
states={'draft': [('readonly', False)]},
|
||||
),
|
||||
'date': fields.date(
|
||||
'Date',
|
||||
required=True,
|
||||
readonly=True,
|
||||
states={'draft': [('readonly', False)]},
|
||||
),
|
||||
# New columns
|
||||
'trans': fields.char('Bank Transaction ID', size=15, required=False,
|
||||
readonly=True,
|
||||
states={'draft':[('readonly', False)]},
|
||||
),
|
||||
'partner_bank_id': fields.many2one('res.partner.bank', 'Bank Account',
|
||||
required=False, readonly=True,
|
||||
states={'draft':[('readonly', False)]},
|
||||
),
|
||||
'period_id': fields.many2one('account.period', 'Period', required=True,
|
||||
states={'confirmed': [('readonly', True)]}),
|
||||
'currency': fields.many2one('res.currency', 'Currency', required=True,
|
||||
states={'confirmed': [('readonly', True)]}),
|
||||
'trans': fields.char(
|
||||
'Bank Transaction ID',
|
||||
size=15,
|
||||
required=False,
|
||||
readonly=True,
|
||||
states={'draft': [('readonly', False)]},
|
||||
),
|
||||
'partner_bank_id': fields.many2one(
|
||||
'res.partner.bank',
|
||||
'Bank Account',
|
||||
required=False,
|
||||
readonly=True,
|
||||
states={'draft': [('readonly', False)]},
|
||||
),
|
||||
'period_id': fields.many2one(
|
||||
'account.period',
|
||||
'Period',
|
||||
required=True,
|
||||
states={'confirmed': [('readonly', True)]},
|
||||
),
|
||||
'currency': fields.many2one(
|
||||
'res.currency',
|
||||
'Currency',
|
||||
required=True,
|
||||
states={'confirmed': [('readonly', True)]},
|
||||
),
|
||||
'reconcile_id': fields.many2one(
|
||||
'account.move.reconcile', 'Reconciliation', readonly=True
|
||||
),
|
||||
'account.move.reconcile',
|
||||
'Reconciliation',
|
||||
readonly=True,
|
||||
),
|
||||
'invoice_id': fields.function(
|
||||
_get_invoice_id, method=True, string='Linked Invoice',
|
||||
type='many2one', relation='account.invoice'
|
||||
),
|
||||
_get_invoice_id,
|
||||
method=True,
|
||||
string='Linked Invoice',
|
||||
type='many2one',
|
||||
relation='account.invoice',
|
||||
),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
@@ -577,9 +640,9 @@ class invoice(orm.Model):
|
||||
_inherit = 'account.invoice'
|
||||
|
||||
def test_undo_paid(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
"""
|
||||
Called from the workflow. Used to unset paid state on
|
||||
invoices that were paid with bank transfers which are being cancelled
|
||||
invoices that were paid with bank transfers which are being cancelled
|
||||
"""
|
||||
for invoice in self.read(cr, uid, ids, ['reconciled'], context):
|
||||
if invoice['reconciled']:
|
||||
@@ -592,22 +655,20 @@ class invoice(orm.Model):
|
||||
'''
|
||||
return [('none', _('Free Reference')),
|
||||
('structured', _('Structured Reference')),
|
||||
]
|
||||
]
|
||||
|
||||
_columns = {
|
||||
'reference_type': fields.selection(_get_reference_type,
|
||||
'Reference Type', required=True
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
invoice()
|
||||
|
||||
|
||||
class account_move_line(orm.Model):
|
||||
_inherit = "account.move.line"
|
||||
|
||||
def get_balance(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
"""
|
||||
Return the balance of any set of move lines.
|
||||
|
||||
Not to be confused with the 'balance' field on this model, which
|
||||
@@ -617,9 +678,6 @@ class account_move_line(orm.Model):
|
||||
if not ids:
|
||||
return total
|
||||
for line in self.read(
|
||||
cr, uid, ids, ['debit', 'credit'], context=context):
|
||||
cr, uid, ids, ['debit', 'credit'], context=context):
|
||||
total += (line['debit'] or 0.0) - (line['credit'] or 0.0)
|
||||
return total
|
||||
account_move_line()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,8 +4,8 @@
|
||||
# Copyright (C) 2011 Therp BV (<http://therp.nl>)
|
||||
#
|
||||
# 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,
|
||||
@@ -24,10 +24,11 @@ __name__ = ("account.bank.statement.line:: set new field 'state' to "
|
||||
"confirmed for all statement lines belonging to confirmed "
|
||||
"statements")
|
||||
|
||||
|
||||
def migrate(cr, version):
|
||||
cr.execute ("UPDATE account_bank_statement_line as sl "
|
||||
" SET state = 'confirmed'"
|
||||
" FROM account_bank_statement as s "
|
||||
" WHERE sl.statement_id = s.id "
|
||||
" AND s.state = 'confirm' "
|
||||
)
|
||||
cr.execute("UPDATE account_bank_statement_line as sl "
|
||||
" SET state = 'confirmed'"
|
||||
" FROM account_bank_statement as s "
|
||||
" WHERE sl.statement_id = s.id "
|
||||
" AND s.state = 'confirm' "
|
||||
)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
|
||||
def migrate(cr, version):
|
||||
if not version:
|
||||
return
|
||||
@@ -26,7 +27,7 @@ def migrate(cr, version):
|
||||
# workflow state moved to another, new module
|
||||
cr.execute(
|
||||
"""
|
||||
UPDATE ir_model_data
|
||||
UPDATE ir_model_data
|
||||
SET module = 'account_banking_payment'
|
||||
WHERE name = 'trans_done_sent'
|
||||
AND module = 'account_direct_debit'
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
|
||||
def migrate(cr, version):
|
||||
if not version:
|
||||
return
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
|
||||
def table_exists(cr, table):
|
||||
""" Check whether a certain table or view exists """
|
||||
cr.execute(
|
||||
@@ -26,6 +27,7 @@ def table_exists(cr, table):
|
||||
(table,))
|
||||
return cr.fetchone()[0] == 1
|
||||
|
||||
|
||||
def migrate(cr, version):
|
||||
"""
|
||||
Migration script for semantic changes in account_banking_payment_export.
|
||||
|
||||
@@ -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,6 +19,6 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import models
|
||||
from . import models
|
||||
|
||||
# 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,
|
||||
@@ -29,14 +29,17 @@ try:
|
||||
except AttributeError:
|
||||
from mx import DateTime as datetime
|
||||
|
||||
|
||||
def str2date(datestr, format='%d/%m/%y'):
|
||||
'''Convert a string to a datatime object'''
|
||||
return datetime.strptime(datestr, format)
|
||||
|
||||
|
||||
def date2str(date, format='%Y-%m-%d'):
|
||||
'''Convert a datetime object to a string'''
|
||||
return date.strftime(format)
|
||||
|
||||
|
||||
def date2date(datestr, fromfmt='%d/%m/%y', tofmt='%Y-%m-%d'):
|
||||
'''
|
||||
Convert a date in a string to another string, in a different
|
||||
@@ -44,7 +47,9 @@ def date2date(datestr, fromfmt='%d/%m/%y', tofmt='%Y-%m-%d'):
|
||||
'''
|
||||
return date2str(str2date(datestr, fromfmt), tofmt)
|
||||
|
||||
_SWIFT = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/-?:().,'+ "
|
||||
_SWIFT = ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
"/-?:().,'+ ")
|
||||
|
||||
|
||||
def to_swift(astr, schemes=['utf-8', 'latin-1', 'ascii']):
|
||||
'''
|
||||
@@ -62,7 +67,7 @@ def to_swift(astr, schemes=['utf-8', 'latin-1', 'ascii']):
|
||||
|
||||
s = [x in _SWIFT and x or ' '
|
||||
for x in unicodedata.normalize('NFKD', astr).encode('ascii', 'ignore')
|
||||
]
|
||||
]
|
||||
return ''.join(s)
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import re
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
||||
class mem_bank_statement(object):
|
||||
'''
|
||||
A mem_bank_statement is a real life projection of a bank statement paper
|
||||
@@ -34,9 +35,15 @@ class mem_bank_statement(object):
|
||||
'''
|
||||
# Lock attributes to enable parsers to trigger non-conformity faults
|
||||
__slots__ = [
|
||||
'start_balance','end_balance', 'date', 'local_account',
|
||||
'local_currency', 'id', 'transactions'
|
||||
'start_balance',
|
||||
'end_balance',
|
||||
'date',
|
||||
'local_account',
|
||||
'local_currency',
|
||||
'id',
|
||||
'transactions'
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(mem_bank_statement, self).__init__(*args, **kwargs)
|
||||
self.id = ''
|
||||
@@ -59,6 +66,7 @@ class mem_bank_statement(object):
|
||||
check += float(transaction.transferred_amount)
|
||||
return abs(check - float(self.end_balance)) < 0.0001
|
||||
|
||||
|
||||
class mem_bank_transaction(object):
|
||||
'''
|
||||
A mem_bank_transaction is a real life copy of a bank transfer. Mapping to
|
||||
@@ -102,7 +110,7 @@ class mem_bank_transaction(object):
|
||||
# remote_currency
|
||||
|
||||
'transferred_amount',
|
||||
# The actual amount transferred -
|
||||
# The actual amount transferred -
|
||||
# negative means sent, positive means received
|
||||
# Most banks use the local_currency to express this amount, but there
|
||||
# may be exceptions I'm unaware of.
|
||||
@@ -126,7 +134,8 @@ class mem_bank_transaction(object):
|
||||
# The other parties postal code belonging to the address
|
||||
|
||||
'remote_owner_country_code',
|
||||
# The other parties two letter ISO country code belonging to the previous
|
||||
# The other parties two letter ISO country code belonging to the
|
||||
# previous
|
||||
|
||||
'remote_owner_custno',
|
||||
# The other parties customer number
|
||||
@@ -175,7 +184,7 @@ class mem_bank_transaction(object):
|
||||
# An error message for interaction with the user
|
||||
# Only used when mem_transaction.valid returns False.
|
||||
'error_message',
|
||||
|
||||
|
||||
# Storno attribute. When True, make the cancelled debit eligible for
|
||||
# a next direct debit run
|
||||
'storno_retry',
|
||||
@@ -213,7 +222,7 @@ class mem_bank_transaction(object):
|
||||
# Will be selected for matching.
|
||||
# STORNO A failed or reversed attempt at direct debit.
|
||||
# Either due to an action on the payer's side
|
||||
# or a failure observed by the bank (lack of
|
||||
# or a failure observed by the bank (lack of
|
||||
# credit for instance)
|
||||
#
|
||||
# Perhaps more will follow.
|
||||
@@ -230,7 +239,7 @@ class mem_bank_transaction(object):
|
||||
DIRECT_DEBIT = 'DD'
|
||||
ORDER = 'DO'
|
||||
PAYMENT_BATCH = 'PB'
|
||||
PAYMENT_TERMINAL = 'PT'
|
||||
PAYMENT_TERMINAL = 'PT'
|
||||
PERIODIC_ORDER = 'PO'
|
||||
STORNO = 'ST'
|
||||
|
||||
@@ -270,7 +279,7 @@ class mem_bank_transaction(object):
|
||||
if value in self.types:
|
||||
self.transfer_type = value
|
||||
else:
|
||||
raise ValueError, _('Invalid value for transfer_type')
|
||||
raise ValueError(_('Invalid value for transfer_type'))
|
||||
|
||||
type = property(_get_type, _set_type)
|
||||
|
||||
@@ -282,6 +291,7 @@ class mem_bank_transaction(object):
|
||||
return (self.execution_date and self.remote_account
|
||||
and self.transferred_amount and True) or False
|
||||
|
||||
|
||||
class parser_type(type):
|
||||
'''
|
||||
Meta annex factory class for house keeping and collecting parsers.
|
||||
@@ -314,11 +324,13 @@ class parser_type(type):
|
||||
keys.sort()
|
||||
return [(parsers[x].code, parsers[x].name) for x in keys]
|
||||
|
||||
|
||||
def create_parser(code):
|
||||
if code in parser_type.parser_by_code:
|
||||
return parser_type.parser_by_code[code]()
|
||||
return None
|
||||
|
||||
|
||||
class parser(object):
|
||||
'''
|
||||
A parser delivers the interface for any parser object. Inherit from
|
||||
@@ -384,7 +396,7 @@ class parser(object):
|
||||
def parse(self, cr, data):
|
||||
'''
|
||||
Parse data.
|
||||
|
||||
|
||||
data is a raw in memory file object. You have to split it in
|
||||
whatever chunks you see fit for parsing. It should return a list
|
||||
of mem_bank_statement objects. Every mem_bank_statement object
|
||||
@@ -400,7 +412,7 @@ class parser(object):
|
||||
be used as a prefix. Adding a tracer (day resolution) can create
|
||||
uniqueness. Adding unique statement ids can add to the robustness of
|
||||
your transaction numbering.
|
||||
|
||||
|
||||
Just mind that users can create random (file)containers with
|
||||
transactions in it. Try not to depend on order of appearance within
|
||||
these files. If in doubt: sort.
|
||||
@@ -408,5 +420,3 @@ class parser(object):
|
||||
raise NotImplementedError(
|
||||
_('This is a stub. Please implement your own.')
|
||||
)
|
||||
|
||||
# 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,
|
||||
@@ -31,16 +31,19 @@ from datetime import datetime, date
|
||||
# Correct python2.4 issues
|
||||
try:
|
||||
datetime.strptime
|
||||
|
||||
def strpdate(str, format):
|
||||
return datetime.strptime(str, format).date()
|
||||
except AttributeError:
|
||||
import time
|
||||
|
||||
def strpdate(str, format):
|
||||
tm = time.strptime(str, format)
|
||||
return date(tm.tm_year, tm.tm_mon, tm.tm_mday)
|
||||
|
||||
import unicodedata
|
||||
|
||||
|
||||
class Field(object):
|
||||
'''Base Field class - fixed length left aligned string field in a record'''
|
||||
def __init__(self, name, length=1, fillchar=' ', cast=str):
|
||||
@@ -57,11 +60,14 @@ class Field(object):
|
||||
|
||||
def take(self, buffer):
|
||||
offset = hasattr(self, 'offset') and self.offset or 0
|
||||
return self.cast(buffer[offset:offset + self.length].rstrip(self.fillchar))
|
||||
return self.cast(buffer[offset:offset + self.length].rstrip(
|
||||
self.fillchar)
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return '%s "%s"' % (self.__class__.__name__, self.name)
|
||||
|
||||
|
||||
class Filler(Field):
|
||||
'''Constant value field'''
|
||||
def __init__(self, name, length=1, value=' '):
|
||||
@@ -73,9 +79,10 @@ class Filler(Field):
|
||||
|
||||
def format(self, value):
|
||||
return super(Filler, self).format(
|
||||
self.value * (self.length / len(self.value) +1)
|
||||
self.value * (self.length / len(self.value) + 1)
|
||||
)
|
||||
|
||||
|
||||
class DateField(Field):
|
||||
'''Variable date field'''
|
||||
def __init__(self, name, format='%Y-%m-%d', auto=False, cast=str):
|
||||
@@ -98,6 +105,7 @@ class DateField(Field):
|
||||
return strpdate(value, self.dateformat)
|
||||
return self.auto and date.today() or None
|
||||
|
||||
|
||||
class RightAlignedField(Field):
|
||||
'''Deviation of Field: right aligned'''
|
||||
def format(self, value):
|
||||
@@ -107,7 +115,10 @@ class RightAlignedField(Field):
|
||||
|
||||
def take(self, buffer):
|
||||
offset = hasattr(self, 'offset') and self.offset or 0
|
||||
return self.cast(buffer[offset:offset + self.length].lstrip(self.fillchar))
|
||||
return self.cast(buffer[offset:offset + self.length].lstrip(
|
||||
self.fillchar)
|
||||
)
|
||||
|
||||
|
||||
class NumberField(RightAlignedField):
|
||||
'''Deviation of Field: left zero filled'''
|
||||
@@ -118,6 +129,7 @@ class NumberField(RightAlignedField):
|
||||
def format(self, value):
|
||||
return super(NumberField, self).format(self.cast(value or ''))
|
||||
|
||||
|
||||
class RecordType(object):
|
||||
fields = []
|
||||
|
||||
@@ -130,7 +142,7 @@ class RecordType(object):
|
||||
offset += field.length
|
||||
|
||||
def __len__(self):
|
||||
return reduce(lambda x,y: x+y.length, self.fields, 0)
|
||||
return reduce(lambda x, y: x + y.length, self.fields, 0)
|
||||
|
||||
def __contains__(self, key):
|
||||
return any(lambda x, y=key: x.name == y, self.fields)
|
||||
@@ -139,7 +151,7 @@ class RecordType(object):
|
||||
for field in self.fields:
|
||||
if field.name == key:
|
||||
return field
|
||||
raise KeyError, 'No such field: %s' % key
|
||||
raise KeyError('No such field: %s' % key)
|
||||
|
||||
def format(self, buffer):
|
||||
result = []
|
||||
@@ -150,7 +162,8 @@ class RecordType(object):
|
||||
def take(self, buffer):
|
||||
return dict(zip([x.name for x in self.fields],
|
||||
[x.take(buffer) for x in self.fields]
|
||||
))
|
||||
))
|
||||
|
||||
|
||||
class Record(object):
|
||||
_recordtype = None
|
||||
@@ -159,12 +172,12 @@ class Record(object):
|
||||
if hasattr(self, '_fields') and self._fields:
|
||||
self._recordtype = RecordType(self._fields)
|
||||
if not self._recordtype and not recordtype:
|
||||
raise ValueError, 'No recordtype specified'
|
||||
raise ValueError('No recordtype specified')
|
||||
if not self._recordtype:
|
||||
self._recordtype = recordtype()
|
||||
self._length = len(self._recordtype)
|
||||
self._value = value.ljust(self._length)[:self._length]
|
||||
|
||||
|
||||
def __len__(self):
|
||||
return self._length
|
||||
|
||||
@@ -173,23 +186,24 @@ class Record(object):
|
||||
super(Record, self).__setattr__(attr, value)
|
||||
else:
|
||||
field = self._recordtype[attr]
|
||||
self._value = self._value[:field.offset] + \
|
||||
field.format(value) + \
|
||||
self._value[field.offset + field.length:]
|
||||
self._value = (
|
||||
self._value[:field.offset] +
|
||||
field.format(value) +
|
||||
self._value[field.offset + field.length:]
|
||||
)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr.startswith('_'):
|
||||
return super(Record, self).__getattr__(attr)
|
||||
field = self._recordtype[attr]
|
||||
return field.take(self._value)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self._recordtype.format(self._value)
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(self.cast(self))
|
||||
|
||||
|
||||
def asciify(str):
|
||||
return unicodedata.normalize('NFKD', str).encode('ascii', 'ignore')
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright 2011 - 2014 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
#
|
||||
# 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
|
||||
@@ -25,7 +25,7 @@ class ResBank(orm.Model):
|
||||
|
||||
def online_bank_info(self, cr, uid, bic, context=None):
|
||||
"""
|
||||
API hook for legacy online lookup of BICs,
|
||||
API hook for legacy online lookup of BICs,
|
||||
to be removed in OpenERP 8.0.
|
||||
"""
|
||||
return False, False
|
||||
|
||||
@@ -30,7 +30,7 @@ class ResPartner(orm.Model):
|
||||
self, cr, uid, ids, get_property_account, context=None):
|
||||
"""
|
||||
Returns the property journal account for the given partners ids.
|
||||
|
||||
|
||||
:param get_property_account: method of this object that takes
|
||||
a partner browse record and returns a field name of type many2one.
|
||||
"""
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
|
||||
__all__ = ['IBAN', 'BBAN']
|
||||
|
||||
|
||||
def modulo_97_base10(abuffer):
|
||||
'''
|
||||
Calculate the modulo 97 value of a string in base10
|
||||
@@ -55,6 +56,7 @@ def modulo_97_base10(abuffer):
|
||||
checksum %= 97
|
||||
return checksum
|
||||
|
||||
|
||||
def base36_to_base10str(abuffer):
|
||||
'''
|
||||
Convert a base36 string value to a string of base10 digits.
|
||||
@@ -67,6 +69,7 @@ def base36_to_base10str(abuffer):
|
||||
result += digit
|
||||
return result
|
||||
|
||||
|
||||
class BBANFormat(object):
|
||||
'''
|
||||
A BBANFormat is an auxilliary class for IBAN. It represents the composition
|
||||
@@ -80,7 +83,7 @@ class BBANFormat(object):
|
||||
Specify the structure of the SEPA account in relation to the local
|
||||
account. The XXZZ prefix that all SEPA accounts have is not part of
|
||||
the structure in BBANFormat.
|
||||
|
||||
|
||||
ibanfmt: string of identifiers from position 5 (start = 1):
|
||||
A = Account position
|
||||
N = Account digit
|
||||
@@ -95,7 +98,7 @@ class BBANFormat(object):
|
||||
leading-zero-stripped account numbers.
|
||||
|
||||
Example: (NL) 'CCCCAAAAAAAAAA'
|
||||
will convert 'INGB0001234567' into
|
||||
will convert 'INGB0001234567' into
|
||||
bankcode 'INGB' and account '0001234567'
|
||||
|
||||
bbanfmt: string of placeholders for the local bank account
|
||||
@@ -119,7 +122,7 @@ class BBANFormat(object):
|
||||
self._iban = ibanfmt
|
||||
self._bban = bbanfmt
|
||||
self._nolz = nolz
|
||||
|
||||
|
||||
def __extract__(self, spec, value):
|
||||
'''Extract the value based on the spec'''
|
||||
i = self._iban.find(spec)
|
||||
@@ -147,7 +150,7 @@ class BBANFormat(object):
|
||||
else:
|
||||
prefix = ''
|
||||
return prefix + self.__extract__('A', iban)
|
||||
|
||||
|
||||
def BBAN(self, iban):
|
||||
'''
|
||||
Format the BBAN part of the IBAN in iban following the local
|
||||
@@ -178,6 +181,7 @@ class BBANFormat(object):
|
||||
i += 1
|
||||
return res
|
||||
|
||||
|
||||
class IBAN(str):
|
||||
'''
|
||||
A IBAN string represents a SEPA bank account number. This class provides
|
||||
@@ -272,7 +276,7 @@ class IBAN(str):
|
||||
if item.isalnum():
|
||||
init += item
|
||||
elif item not in ' \t.-':
|
||||
raise ValueError, 'Invalid chars found in IBAN number'
|
||||
raise ValueError('Invalid chars found in IBAN number')
|
||||
return str.__new__(cls, init)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -287,8 +291,7 @@ class IBAN(str):
|
||||
|
||||
@classmethod
|
||||
def create(cls, BIC=None, countrycode=None, BBAN=None, bankcode=None,
|
||||
branchcode=None, account=None
|
||||
):
|
||||
branchcode=None, account=None):
|
||||
'''
|
||||
Create a IBAN number from a BBAN and a country code. Optionaly create
|
||||
a BBAN from BBAN components before generation.
|
||||
@@ -304,20 +307,17 @@ class IBAN(str):
|
||||
if countrycode:
|
||||
countrycode = countrycode.upper()
|
||||
else:
|
||||
raise ValueError, \
|
||||
'Either BIC or countrycode is required'
|
||||
raise ValueError('Either BIC or countrycode is required')
|
||||
|
||||
if countrycode not in cls.countries:
|
||||
raise ValueError, \
|
||||
'%s is not a SEPA country' % countrycode
|
||||
raise ValueError('%s is not a SEPA country' % countrycode)
|
||||
format = cls.BBAN_formats[countrycode]
|
||||
|
||||
if BBAN:
|
||||
if len(BBAN) == len(format._iban):
|
||||
ibanno = cls(countrycode + '00' + BBAN)
|
||||
return cls(countrycode + ibanno.checksum + BBAN)
|
||||
raise ValueError, \
|
||||
'Insufficient data to generate IBAN'
|
||||
raise ValueError('Insufficient data to generate IBAN')
|
||||
|
||||
@property
|
||||
def valid(self):
|
||||
@@ -325,8 +325,10 @@ class IBAN(str):
|
||||
Check if the string + check digits deliver a valid checksum
|
||||
'''
|
||||
_buffer = self[4:] + self[:4]
|
||||
return self.countrycode in self.countries and \
|
||||
int(base36_to_base10str(_buffer)) % 97 == 1
|
||||
return (
|
||||
self.countrycode in self.countries
|
||||
and int(base36_to_base10str(_buffer)) % 97 == 1
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
'''
|
||||
@@ -387,7 +389,7 @@ class IBAN(str):
|
||||
The bank code seems to be world wide unique. Knowing this,
|
||||
one can use the country + bankcode info from BIC to narrow a
|
||||
search for the bank itself.
|
||||
|
||||
|
||||
Note that some countries use one single localization code for
|
||||
all bank transactions in that country, while others do not. This
|
||||
makes it impossible to use an algorithmic approach for generating
|
||||
@@ -421,22 +423,24 @@ class IBAN(str):
|
||||
'''
|
||||
return self[4:]
|
||||
|
||||
|
||||
class BBAN(object):
|
||||
'''
|
||||
Class to reformat a local BBAN account number to IBAN specs.
|
||||
Simple validation based on length of spec string elements and real data.
|
||||
'''
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _get_length(fmt, element):
|
||||
'''
|
||||
Internal method to calculate the length of a parameter in a
|
||||
formatted string
|
||||
'''
|
||||
i = 0; max_i = len(fmt._iban)
|
||||
i = 0
|
||||
max_i = len(fmt._iban)
|
||||
while i < max_i:
|
||||
if fmt._iban[i] == element:
|
||||
next = i +1
|
||||
next = i + 1
|
||||
while next < max_i and fmt._iban[next] == element:
|
||||
next += 1
|
||||
return next - i
|
||||
@@ -453,7 +457,10 @@ class BBAN(object):
|
||||
if countrycode.upper() in IBAN.countries:
|
||||
self._fmt = IBAN.BBAN_formats[countrycode.upper()]
|
||||
res = ''
|
||||
i = 0; j = 0; max_i = len(self._fmt._bban); max_j = len(bban)
|
||||
i = 0
|
||||
j = 0
|
||||
max_i = len(self._fmt._bban)
|
||||
max_j = len(bban)
|
||||
while i < max_i and j < max_j:
|
||||
while bban[j] in ' \t' and j < max_j:
|
||||
j += 1
|
||||
@@ -475,7 +482,7 @@ class BBAN(object):
|
||||
# Note that many accounts in the IBAN standard
|
||||
# are allowed to have leading zeros, so zfill
|
||||
# to full spec length for visual validation.
|
||||
#
|
||||
#
|
||||
# Note 2: this may look funny to some, as most
|
||||
# local schemes strip leading zeros. It allows
|
||||
# us however to present the user a visual feedback
|
||||
@@ -512,15 +519,16 @@ class BBAN(object):
|
||||
'''Simple check if BBAN is in the right format'''
|
||||
return self._bban and True or False
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
for arg in sys.argv[1:]:
|
||||
iban = IBAN(arg)
|
||||
print 'IBAN:', iban
|
||||
print 'country code:', iban.countrycode
|
||||
print 'bank code:', iban.bankcode
|
||||
print 'branch code:', iban.branchcode
|
||||
print 'BBAN:', iban.BBAN
|
||||
print 'localized BBAN:', iban.localized_BBAN
|
||||
print 'check digits:', iban.checkdigits
|
||||
print 'checksum:', iban.checksum
|
||||
print('IBAN:', iban)
|
||||
print('country code:', iban.countrycode)
|
||||
print('bank code:', iban.bankcode)
|
||||
print('branch code:', iban.branchcode)
|
||||
print('BBAN:', iban.BBAN)
|
||||
print('localized BBAN:', iban.localized_BBAN)
|
||||
print('check digits:', iban.checkdigits)
|
||||
print('checksum:', iban.checksum)
|
||||
|
||||
@@ -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,
|
||||
@@ -27,6 +27,7 @@ import re
|
||||
|
||||
__all__ = ['split', 'get', 'PostalCode']
|
||||
|
||||
|
||||
class PostalCode(object):
|
||||
'''
|
||||
The PostalCode class is a wrapper around PostCodeFormat and an internal
|
||||
@@ -46,11 +47,11 @@ class PostalCode(object):
|
||||
'''
|
||||
# Sort formats on length, longest first
|
||||
formats = [(len(x), x) for x in format.split('|')]
|
||||
formats = [x[1] for x in sorted(formats, lambda x,y: -cmp(x,y))]
|
||||
self.res = [re.compile(x.replace('#', '\\d').replace('@','[A-Z]'))
|
||||
formats = [x[1] for x in sorted(formats, lambda x, y: -cmp(x, y))]
|
||||
self.res = [re.compile(x.replace('#', '\\d').replace('@', '[A-Z]'))
|
||||
for x in formats
|
||||
]
|
||||
|
||||
]
|
||||
|
||||
def get(self, str_):
|
||||
'''
|
||||
Return the postal code from the string str_
|
||||
@@ -99,7 +100,8 @@ class PostalCode(object):
|
||||
'IM': '@# #@@|@## #@@|@@# #@@|@@## #@@|@#@ #@@|@@#@ #@@|GIR0AA',
|
||||
'IL': '#####', 'IT': '####', 'JM': '', 'JP': '###-####',
|
||||
'JE': '@# #@@|@## #@@|@@# #@@|@@## #@@|@#@ #@@|@@#@ #@@|GIR0AA',
|
||||
'JO': '#####', 'KZ': '######', 'KE': '#####', 'KI': '', 'KP': '###-###',
|
||||
'JO': '#####', 'KZ': '######', 'KE': '#####', 'KI': '',
|
||||
'KP': '###-###',
|
||||
'KR': 'SEOUL ###-###', 'KW': '#####', 'KG': '######', 'LA': '#####',
|
||||
'LV': 'LV-####', 'LB': '#### ####|####', 'LS': '###', 'LR': '####',
|
||||
'LY': '', 'LI': '####', 'LT': 'LT-#####', 'LU': '####', 'MO': '',
|
||||
@@ -139,7 +141,7 @@ class PostalCode(object):
|
||||
country <iso>.
|
||||
|
||||
Returns iso, postal code and the remaining part of <str_>.
|
||||
|
||||
|
||||
When iso is filled but postal code remains empty, no postal code could
|
||||
be found according to the rules of iso.
|
||||
|
||||
@@ -155,14 +157,12 @@ class PostalCode(object):
|
||||
|
||||
# Find optimum (= max length postalcode) when iso code is unknown
|
||||
all = {}
|
||||
opt_iso = ''
|
||||
max_l = 0
|
||||
for key in cls._formats.iterkeys():
|
||||
i, p, c = cls.split(str_, key)
|
||||
l = len(p)
|
||||
if l > max_l:
|
||||
max_l = l
|
||||
opt_iso = i
|
||||
if l in all:
|
||||
all[l].append((i, p, c))
|
||||
else:
|
||||
|
||||
@@ -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,
|
||||
@@ -18,6 +18,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
'''
|
||||
Define a struct class which behaves like a dict, but allows using
|
||||
object.attr alongside object['attr'].
|
||||
@@ -25,6 +26,7 @@ object.attr alongside object['attr'].
|
||||
|
||||
__all__ = ['struct']
|
||||
|
||||
|
||||
class struct(dict):
|
||||
'''
|
||||
Ease working with dicts. Allow dict.key alongside dict['key']
|
||||
@@ -52,4 +54,4 @@ class struct(dict):
|
||||
else:
|
||||
fmt = '%*.*s%%s: %%s' % (indent, indent, '')
|
||||
for item in self.iteritems():
|
||||
print fmt % item
|
||||
print(fmt % item)
|
||||
|
||||
@@ -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,
|
||||
@@ -18,8 +18,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
import bank_import
|
||||
import banking_transaction_wizard
|
||||
import link_partner
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
from . import bank_import
|
||||
from . import banking_transaction_wizard
|
||||
from . import link_partner
|
||||
|
||||
@@ -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,
|
||||
@@ -23,11 +23,13 @@
|
||||
# Kaspars Vilkens (KNdati): lenghty discussions, bugreports and bugfixes
|
||||
# Stefan Rijnhart (Therp): bugreport and bugfix
|
||||
#
|
||||
|
||||
'''
|
||||
This module contains the business logic of the wizard account_banking_import.
|
||||
The parsing is done in the parser modules. Every parser module is required to
|
||||
use parser.models as a mean of communication with the business logic.
|
||||
'''
|
||||
|
||||
import base64
|
||||
import datetime
|
||||
from openerp.osv import orm, fields
|
||||
@@ -44,6 +46,7 @@ bt = models.mem_bank_transaction
|
||||
# the real payment date. This can occur with online transactions (web shops).
|
||||
payment_window = datetime.timedelta(days=10)
|
||||
|
||||
|
||||
def parser_types(*args, **kwargs):
|
||||
'''Delay evaluation of parser types until start of wizard, to allow
|
||||
depending modules to initialize and add their parsers to the list
|
||||
@@ -57,18 +60,26 @@ class banking_import_line(orm.TransientModel):
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=64),
|
||||
'date': fields.date('Date', readonly=True),
|
||||
'amount': fields.float('Amount', digits_compute=dp.get_precision('Account')),
|
||||
'amount': fields.float(
|
||||
'Amount',
|
||||
digits_compute=dp.get_precision('Account'),
|
||||
),
|
||||
'statement_line_id': fields.many2one(
|
||||
'account.bank.statement.line',
|
||||
'Resulting statement line', readonly=True),
|
||||
'type': fields.selection([
|
||||
('supplier','Supplier'),
|
||||
('customer','Customer'),
|
||||
('general','General')
|
||||
('supplier', 'Supplier'),
|
||||
('customer', 'Customer'),
|
||||
('general', 'General')
|
||||
], 'Type', required=True),
|
||||
'partner_id': fields.many2one('res.partner', 'Partner'),
|
||||
'statement_id': fields.many2one('account.bank.statement', 'Statement',
|
||||
select=True, required=True, ondelete='cascade'),
|
||||
'statement_id': fields.many2one(
|
||||
'account.bank.statement',
|
||||
'Statement',
|
||||
select=True,
|
||||
required=True,
|
||||
ondelete='cascade',
|
||||
),
|
||||
'ref': fields.char('Reference', size=32),
|
||||
'note': fields.text('Notes'),
|
||||
'period_id': fields.many2one('account.period', 'Period'),
|
||||
@@ -83,16 +94,19 @@ class banking_import_line(orm.TransientModel):
|
||||
'account.invoice', 'banking_import_line_invoice_rel',
|
||||
'line_id', 'invoice_id'),
|
||||
'partner_bank_id': fields.many2one('res.partner.bank', 'Bank Account'),
|
||||
'transaction_type': fields.selection([
|
||||
'transaction_type': fields.selection(
|
||||
[
|
||||
# TODO: payment terminal etc...
|
||||
('invoice', 'Invoice payment'),
|
||||
('storno', 'Canceled debit order'),
|
||||
('bank_costs', 'Bank costs'),
|
||||
('unknown', 'Unknown'),
|
||||
], 'Transaction type'),
|
||||
],
|
||||
'Transaction type',
|
||||
),
|
||||
'duplicate': fields.boolean('Duplicate'),
|
||||
}
|
||||
|
||||
|
||||
|
||||
class banking_import(orm.TransientModel):
|
||||
_name = 'account.banking.bank.import'
|
||||
@@ -119,7 +133,8 @@ class banking_import(orm.TransientModel):
|
||||
if not parser:
|
||||
raise orm.except_orm(
|
||||
_('ERROR!'),
|
||||
_('Unable to import parser %(parser)s. Parser class not found.') %
|
||||
_('Unable to import parser %(parser)s. Parser class not '
|
||||
'found.') %
|
||||
{'parser': parser_code}
|
||||
)
|
||||
|
||||
@@ -133,16 +148,17 @@ class banking_import(orm.TransientModel):
|
||||
if any([x for x in statements if not x.is_valid()]):
|
||||
raise orm.except_orm(
|
||||
_('ERROR!'),
|
||||
_('The imported statements appear to be invalid! Check your file.')
|
||||
_('The imported statements appear to be invalid! Check your '
|
||||
'file.')
|
||||
)
|
||||
|
||||
# Create the file now, as the statements need to be linked to it
|
||||
import_id = statement_file_obj.create(cr, uid, dict(
|
||||
company_id = company.id,
|
||||
file = statements_file,
|
||||
file_name = banking_import.file_name,
|
||||
state = 'unfinished',
|
||||
format = parser.name,
|
||||
company_id=company.id,
|
||||
file=statements_file,
|
||||
file_name=banking_import.file_name,
|
||||
state='unfinished',
|
||||
format=parser.name,
|
||||
))
|
||||
|
||||
bank_country_code = False
|
||||
@@ -151,14 +167,14 @@ class banking_import(orm.TransientModel):
|
||||
|
||||
# Results
|
||||
results = struct(
|
||||
stat_loaded_cnt = 0,
|
||||
trans_loaded_cnt = 0,
|
||||
stat_skipped_cnt = 0,
|
||||
trans_skipped_cnt = 0,
|
||||
trans_matched_cnt = 0,
|
||||
bank_costs_invoice_cnt = 0,
|
||||
error_cnt = 0,
|
||||
log = [],
|
||||
stat_loaded_cnt=0,
|
||||
trans_loaded_cnt=0,
|
||||
stat_skipped_cnt=0,
|
||||
trans_skipped_cnt=0,
|
||||
trans_matched_cnt=0,
|
||||
bank_costs_invoice_cnt=0,
|
||||
error_cnt=0,
|
||||
log=[],
|
||||
)
|
||||
|
||||
# Caching
|
||||
@@ -175,7 +191,9 @@ class banking_import(orm.TransientModel):
|
||||
continue
|
||||
|
||||
# Create fallback currency code
|
||||
currency_code = statement.local_currency or company.currency_id.name
|
||||
currency_code = (
|
||||
statement.local_currency or company.currency_id.name
|
||||
)
|
||||
|
||||
# Check cache for account info/currency
|
||||
if statement.local_account in info and \
|
||||
@@ -190,8 +208,10 @@ class banking_import(orm.TransientModel):
|
||||
)
|
||||
if not account_info:
|
||||
results.log.append(
|
||||
_('Statements found for unknown account %(bank_account)s') %
|
||||
{'bank_account': statement.local_account}
|
||||
_('Statements found for unknown account '
|
||||
'%(bank_account)s') % {
|
||||
'bank_account': statement.local_account
|
||||
}
|
||||
)
|
||||
error_accounts[statement.local_account] = True
|
||||
results.error_cnt += 1
|
||||
@@ -200,7 +220,7 @@ class banking_import(orm.TransientModel):
|
||||
results.log.append(
|
||||
_('Statements found for account %(bank_account)s, '
|
||||
'but no default journal was defined.'
|
||||
) % {'bank_account': statement.local_account}
|
||||
) % {'bank_account': statement.local_account}
|
||||
)
|
||||
error_accounts[statement.local_account] = True
|
||||
results.error_cnt += 1
|
||||
@@ -210,7 +230,7 @@ class banking_import(orm.TransientModel):
|
||||
currency_code = account_info.currency_id.name
|
||||
|
||||
# Cache results
|
||||
if not statement.local_account in info:
|
||||
if statement.local_account not in info:
|
||||
info[statement.local_account] = {
|
||||
currency_code: account_info
|
||||
}
|
||||
@@ -222,22 +242,22 @@ class banking_import(orm.TransientModel):
|
||||
and account_info.currency_id.name != statement.local_currency:
|
||||
# TODO: convert currencies?
|
||||
results.log.append(
|
||||
_('Statement %(statement_id)s for account %(bank_account)s'
|
||||
_('Statement %(statement_id)s for account %(bank_account)s'
|
||||
' uses different currency than the defined bank journal.'
|
||||
) % {
|
||||
'bank_account': statement.local_account,
|
||||
'statement_id': statement.id
|
||||
}
|
||||
) % {
|
||||
'bank_account': statement.local_account,
|
||||
'statement_id': statement.id
|
||||
}
|
||||
)
|
||||
error_accounts[statement.local_account] = True
|
||||
results.error_cnt += 1
|
||||
continue
|
||||
|
||||
# Check existence of previous statement
|
||||
# Less well defined formats can resort to a
|
||||
# Less well defined formats can resort to a
|
||||
# dynamically generated statement identification
|
||||
# (e.g. a datetime string of the moment of import)
|
||||
# and have potential duplicates flagged by the
|
||||
# and have potential duplicates flagged by the
|
||||
# matching procedure
|
||||
statement_ids = statement_obj.search(cr, uid, [
|
||||
('name', '=', statement.id),
|
||||
@@ -251,7 +271,8 @@ class banking_import(orm.TransientModel):
|
||||
)
|
||||
continue
|
||||
|
||||
# Get the period for the statement (as bank statement object checks this)
|
||||
# Get the period for the statement (as bank statement object
|
||||
# checks this)
|
||||
period_ids = period_obj.search(
|
||||
cr, uid, [
|
||||
('company_id', '=', company.id),
|
||||
@@ -259,7 +280,7 @@ class banking_import(orm.TransientModel):
|
||||
('date_stop', '>=', statement.date),
|
||||
('special', '=', False),
|
||||
], context=context)
|
||||
|
||||
|
||||
if not period_ids:
|
||||
results.log.append(
|
||||
_('No period found covering statement date %(date)s, '
|
||||
@@ -272,17 +293,17 @@ class banking_import(orm.TransientModel):
|
||||
|
||||
# Create the bank statement record
|
||||
statement_id = statement_obj.create(cr, uid, dict(
|
||||
name = statement.id,
|
||||
journal_id = account_info.journal_id.id,
|
||||
date = convert.date2str(statement.date),
|
||||
balance_start = statement.start_balance,
|
||||
balance_end_real = statement.end_balance,
|
||||
balance_end = statement.end_balance,
|
||||
state = 'draft',
|
||||
user_id = uid,
|
||||
banking_id = import_id,
|
||||
company_id = company.id,
|
||||
period_id = period_ids[0],
|
||||
name=statement.id,
|
||||
journal_id=account_info.journal_id.id,
|
||||
date=convert.date2str(statement.date),
|
||||
balance_start=statement.start_balance,
|
||||
balance_end_real=statement.end_balance,
|
||||
balance_end=statement.end_balance,
|
||||
state='draft',
|
||||
user_id=uid,
|
||||
banking_id=import_id,
|
||||
company_id=company.id,
|
||||
period_id=period_ids[0],
|
||||
))
|
||||
imported_statement_ids.append(statement_id)
|
||||
|
||||
@@ -294,7 +315,8 @@ class banking_import(orm.TransientModel):
|
||||
values = {}
|
||||
for attr in transaction.__slots__ + ['type']:
|
||||
if attr in import_transaction_obj.column_map:
|
||||
values[import_transaction_obj.column_map[attr]] = eval('transaction.%s' % attr)
|
||||
values[import_transaction_obj.column_map[attr]] = \
|
||||
eval('transaction.%s' % attr)
|
||||
elif attr in import_transaction_obj._columns:
|
||||
values[attr] = eval('transaction.%s' % attr)
|
||||
values['statement_id'] = statement_id
|
||||
@@ -305,35 +327,17 @@ class banking_import(orm.TransientModel):
|
||||
transaction_id = import_transaction_obj.create(
|
||||
cr, uid, values, context=context)
|
||||
transaction_ids.append(transaction_id)
|
||||
|
||||
|
||||
results.stat_loaded_cnt += 1
|
||||
|
||||
import_transaction_obj.match(cr, uid, transaction_ids, results=results, context=context)
|
||||
|
||||
#recompute statement end_balance for validation
|
||||
import_transaction_obj.match(
|
||||
cr, uid, transaction_ids, results=results, context=context
|
||||
)
|
||||
|
||||
# recompute statement end_balance for validation
|
||||
statement_obj.button_dummy(
|
||||
cr, uid, imported_statement_ids, context=context)
|
||||
|
||||
|
||||
# Original code. Didn't take workflow logistics into account...
|
||||
#
|
||||
#cr.execute(
|
||||
# "UPDATE payment_order o "
|
||||
# "SET state = 'done', "
|
||||
# "date_done = '%s' "
|
||||
# "FROM payment_line l "
|
||||
# "WHERE o.state = 'sent' "
|
||||
# "AND o.id = l.order_id "
|
||||
# "AND l.id NOT IN ("
|
||||
# "SELECT DISTINCT id FROM payment_line "
|
||||
# "WHERE date_done IS NULL "
|
||||
# "AND id IN (%s)"
|
||||
# ")" % (
|
||||
# time.strftime('%Y-%m-%d'),
|
||||
# ','.join([str(x) for x in payment_line_ids])
|
||||
# )
|
||||
#)
|
||||
|
||||
report = [
|
||||
'%s: %s' % (_('Total number of statements'),
|
||||
results.stat_skipped_cnt + results.stat_loaded_cnt),
|
||||
@@ -360,15 +364,18 @@ class banking_import(orm.TransientModel):
|
||||
text_log = '\n'.join(report + results.log)
|
||||
state = results.error_cnt and 'error' or 'ready'
|
||||
statement_file_obj.write(cr, uid, import_id, dict(
|
||||
state = state, log = text_log,
|
||||
), context)
|
||||
state=state,
|
||||
log=text_log,
|
||||
), context)
|
||||
if not imported_statement_ids or not results.trans_loaded_cnt:
|
||||
# file state can be 'ready' while import state is 'error'
|
||||
state = 'error'
|
||||
self.write(cr, uid, [ids[0]], dict(
|
||||
import_id = import_id, log = text_log, state = state,
|
||||
statement_ids = [(6, 0, imported_statement_ids)],
|
||||
), context)
|
||||
import_id=import_id,
|
||||
log=text_log,
|
||||
state=state,
|
||||
statement_ids=[(6, 0, imported_statement_ids)],
|
||||
), context)
|
||||
return {
|
||||
'name': (state == 'ready' and _('Review Bank Statements') or
|
||||
_('Error')),
|
||||
@@ -393,13 +400,15 @@ class banking_import(orm.TransientModel):
|
||||
),
|
||||
'file_name': fields.char('File name', size=256),
|
||||
'file': fields.binary(
|
||||
'Statements File', required=True,
|
||||
help = ('The Transactions File to import. Please note that while it is '
|
||||
'perfectly safe to reload the same file multiple times or to load in '
|
||||
'timeframe overlapping statements files, there are formats that may '
|
||||
'introduce different sequencing, which may create double entries.\n\n'
|
||||
'To stay on the safe side, always load bank statements files using the '
|
||||
'same format.'),
|
||||
'Statements File',
|
||||
required=True,
|
||||
help=('The Transactions File to import. Please note that while it '
|
||||
'is perfectly safe to reload the same file multiple times '
|
||||
'or to load in timeframe overlapping statements files, '
|
||||
'there are formats that may introduce different '
|
||||
'sequencing, which may create double entries.\n\n'
|
||||
'To stay on the safe side, always load bank statements '
|
||||
'files using the same format.'),
|
||||
states={
|
||||
'ready': [('readonly', True)],
|
||||
'error': [('readonly', True)],
|
||||
@@ -435,8 +444,8 @@ class banking_import(orm.TransientModel):
|
||||
|
||||
_defaults = {
|
||||
'state': 'init',
|
||||
'company': lambda s,cr,uid,c:
|
||||
'company': lambda s, cr, uid, c:
|
||||
s.pool.get('res.company')._company_default_get(
|
||||
cr, uid, 'bank.import.transaction', context=c),
|
||||
cr, uid, 'bank.import.transaction', context=c),
|
||||
'parser': _default_parser_type,
|
||||
}
|
||||
|
||||
@@ -7,8 +7,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,
|
||||
@@ -50,7 +50,7 @@ class banking_transaction_wizard(orm.TransientModel):
|
||||
return res
|
||||
|
||||
def create_act_window(self, cr, uid, ids, nodestroy=True, context=None):
|
||||
"""
|
||||
"""
|
||||
Return a popup window for this model
|
||||
"""
|
||||
if isinstance(ids, (int, long)):
|
||||
@@ -78,10 +78,10 @@ class banking_transaction_wizard(orm.TransientModel):
|
||||
import_transaction_obj = self.pool.get('banking.import.transaction')
|
||||
trans_id = self.read(
|
||||
cr, uid, ids[0], ['import_transaction_id'],
|
||||
context=context)['import_transaction_id'][0] # many2one tuple
|
||||
context=context)['import_transaction_id'][0] # many2one tuple
|
||||
import_transaction_obj.match(cr, uid, [trans_id], context=context)
|
||||
return self.create_act_window(cr, uid, ids, context=None)
|
||||
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
"""
|
||||
Implement a trigger to retrieve the corresponding move line
|
||||
@@ -121,22 +121,27 @@ class banking_transaction_wizard(orm.TransientModel):
|
||||
# Given the arity of the relation, there is are always
|
||||
# multiple possibilities but the move lines here are
|
||||
# prefiltered for having account_id.type payable/receivable
|
||||
# and the regular invoice workflow should only come up with
|
||||
# and the regular invoice workflow should only come up with
|
||||
# one of those only.
|
||||
for move_line in wiz.import_transaction_id.move_line_ids:
|
||||
if (move_line.invoice ==
|
||||
wiz.import_transaction_id.invoice_id):
|
||||
transaction_obj.write(
|
||||
cr, uid, wiz.import_transaction_id.id,
|
||||
{ 'move_line_id': move_line.id, }, context=context)
|
||||
{'move_line_id': move_line.id, },
|
||||
context=context
|
||||
)
|
||||
statement_line_obj.write(
|
||||
cr, uid, wiz.import_transaction_id.statement_line_id.id,
|
||||
{ 'partner_id': move_line.partner_id.id or False,
|
||||
'account_id': move_line.account_id.id,
|
||||
}, context=context)
|
||||
cr, uid,
|
||||
wiz.import_transaction_id.statement_line_id.id,
|
||||
{
|
||||
'partner_id': (
|
||||
move_line.partner_id.id or False),
|
||||
'account_id': move_line.account_id.id,
|
||||
}, context=context)
|
||||
found = True
|
||||
break
|
||||
# Cannot match the invoice
|
||||
# Cannot match the invoice
|
||||
if not found:
|
||||
orm.except_orm(
|
||||
_("No entry found for the selected invoice"),
|
||||
@@ -150,15 +155,16 @@ class banking_transaction_wizard(orm.TransientModel):
|
||||
# Rewrite *2many directive notation
|
||||
if manual_invoice_ids:
|
||||
manual_invoice_ids = (
|
||||
[i[1] for i in manual_invoice_ids if i[0]==4] +
|
||||
[j for i in manual_invoice_ids if i[0]==6 for j in i[2]])
|
||||
[i[1] for i in manual_invoice_ids if i[0] == 4] +
|
||||
[j for i in manual_invoice_ids if i[0] == 6 for j in i[2]])
|
||||
if manual_move_line_ids:
|
||||
manual_move_line_ids = (
|
||||
[i[1] for i in manual_move_line_ids if i[0]==4] +
|
||||
[j for i in manual_move_line_ids if i[0]==6 for j in i[2]])
|
||||
[i[1] for i in manual_move_line_ids if i[0] == 4] +
|
||||
[j for i in manual_move_line_ids
|
||||
if i[0] == 6 for j in i[2]])
|
||||
for wiz in self.browse(cr, uid, ids, context=context):
|
||||
#write can be called multiple times for the same values
|
||||
#that doesn't hurt above, but it does here
|
||||
# write can be called multiple times for the same values
|
||||
# that doesn't hurt above, but it does here
|
||||
if wiz.match_type and (
|
||||
len(manual_move_line_ids) > 1 or
|
||||
len(manual_invoice_ids) > 1):
|
||||
@@ -171,7 +177,8 @@ class banking_transaction_wizard(orm.TransientModel):
|
||||
found_move_line = False
|
||||
if invoice.move_id:
|
||||
for line in invoice.move_id.line_id:
|
||||
if line.account_id.type in ('receivable', 'payable'):
|
||||
if line.account_id.type in ('receivable',
|
||||
'payable'):
|
||||
todo.append((invoice.id, line.id))
|
||||
found_move_line = True
|
||||
break
|
||||
@@ -181,12 +188,13 @@ class banking_transaction_wizard(orm.TransientModel):
|
||||
_("No entry found for the selected invoice. "))
|
||||
for move_line_id in manual_move_line_ids:
|
||||
todo_entry = [False, move_line_id]
|
||||
move_line=move_line_obj.read(
|
||||
cr,
|
||||
uid,
|
||||
move_line_id,
|
||||
['invoice'],
|
||||
context=context)
|
||||
move_line = move_line_obj.read(
|
||||
cr,
|
||||
uid,
|
||||
move_line_id,
|
||||
['invoice'],
|
||||
context=context
|
||||
)
|
||||
if move_line['invoice']:
|
||||
todo_entry[0] = move_line['invoice'][0]
|
||||
todo.append(todo_entry)
|
||||
@@ -194,25 +202,27 @@ class banking_transaction_wizard(orm.TransientModel):
|
||||
while todo:
|
||||
todo_entry = todo.pop()
|
||||
move_line = move_line_obj.browse(
|
||||
cr, uid, todo_entry[1], context)
|
||||
cr, uid, todo_entry[1], context)
|
||||
transaction_id = wiz.import_transaction_id.id
|
||||
statement_line_id = wiz.statement_line_id.id
|
||||
|
||||
if len(todo) > 0:
|
||||
statement_line_id = wiz.statement_line_id.split_off(
|
||||
move_line.debit or -move_line.credit)[0]
|
||||
move_line.debit or -move_line.credit)[0]
|
||||
transaction_id = statement_line_obj.browse(
|
||||
cr,
|
||||
uid,
|
||||
statement_line_id,
|
||||
context=context).import_transaction_id.id
|
||||
cr,
|
||||
uid,
|
||||
statement_line_id,
|
||||
context=context
|
||||
).import_transaction_id.id
|
||||
|
||||
vals = {
|
||||
'move_line_id': todo_entry[1],
|
||||
'move_line_ids': [(6, 0, [todo_entry[1]])],
|
||||
'invoice_id': todo_entry[0],
|
||||
'invoice_ids': [(6, 0,
|
||||
[todo_entry[0]] if todo_entry[0] else [])],
|
||||
'invoice_ids': [
|
||||
(6, 0, [todo_entry[0]] if todo_entry[0] else [])
|
||||
],
|
||||
'match_type': 'manual',
|
||||
}
|
||||
|
||||
@@ -221,7 +231,7 @@ class banking_transaction_wizard(orm.TransientModel):
|
||||
|
||||
st_line_vals = {
|
||||
'account_id': move_line_obj.read(
|
||||
cr, uid, todo_entry[1],
|
||||
cr, uid, todo_entry[1],
|
||||
['account_id'], context=context)['account_id'][0],
|
||||
}
|
||||
|
||||
@@ -231,7 +241,7 @@ class banking_transaction_wizard(orm.TransientModel):
|
||||
).partner_id.commercial_partner_id.id
|
||||
|
||||
statement_line_obj.write(
|
||||
cr, uid, statement_line_id,
|
||||
cr, uid, statement_line_id,
|
||||
st_line_vals, context=context)
|
||||
return res
|
||||
|
||||
@@ -255,22 +265,19 @@ class banking_transaction_wizard(orm.TransientModel):
|
||||
# Get the bank account setting record, to reset the account
|
||||
account_id = False
|
||||
journal_id = wiz.statement_line_id.statement_id.journal_id.id
|
||||
setting_ids = settings_pool.find(cr, uid, journal_id, context=context)
|
||||
setting_ids = settings_pool.find(
|
||||
cr, uid, journal_id, context=context
|
||||
)
|
||||
|
||||
# Restore partner id from the bank account or else reset
|
||||
partner_id = False
|
||||
if (wiz.statement_line_id.partner_bank_id and
|
||||
wiz.statement_line_id.partner_bank_id.partner_id):
|
||||
partner_id = wiz.statement_line_id.partner_bank_id.partner_id.id
|
||||
partner_id = (
|
||||
wiz.statement_line_id.partner_bank_id.partner_id.id
|
||||
)
|
||||
wiz.write({'partner_id': partner_id})
|
||||
|
||||
# Select account type by parter customer or supplier,
|
||||
# or default based on amount sign
|
||||
if wiz.amount < 0:
|
||||
account_type = 'payable'
|
||||
else:
|
||||
account_type = 'receivable'
|
||||
|
||||
bank_partner = False
|
||||
if partner_id:
|
||||
bank_partner = wiz.statement_line_id.partner_bank_id.partner_id
|
||||
@@ -295,13 +302,18 @@ class banking_transaction_wizard(orm.TransientModel):
|
||||
wiz.statement_line_id.write({'account_id': account_id})
|
||||
|
||||
if wiz.statement_line_id:
|
||||
#delete splits causing an unsplit if this is a split
|
||||
#transaction
|
||||
statement_pool.unlink(cr, uid,
|
||||
statement_pool.search(cr, uid,
|
||||
[('parent_id', '=', wiz.statement_line_id.id)],
|
||||
context=context),
|
||||
context=context)
|
||||
# delete splits causing an unsplit if this is a split
|
||||
# transaction
|
||||
statement_pool.unlink(
|
||||
cr,
|
||||
uid,
|
||||
statement_pool.search(
|
||||
cr, uid,
|
||||
[('parent_id', '=', wiz.statement_line_id.id)],
|
||||
context=context
|
||||
),
|
||||
context=context
|
||||
)
|
||||
|
||||
if wiz.import_transaction_id:
|
||||
wiz.import_transaction_id.clear_and_write()
|
||||
@@ -313,15 +325,15 @@ class banking_transaction_wizard(orm.TransientModel):
|
||||
ids = [ids]
|
||||
transaction_obj = self.pool.get('banking.import.transaction')
|
||||
for wiz in self.read(
|
||||
cr, uid, ids, ['duplicate', 'import_transaction_id'],
|
||||
context=context):
|
||||
cr, uid, ids, ['duplicate', 'import_transaction_id'],
|
||||
context=context):
|
||||
transaction_obj.write(
|
||||
cr, uid, wiz['import_transaction_id'][0],
|
||||
cr, uid, wiz['import_transaction_id'][0],
|
||||
{'duplicate': not wiz['duplicate']}, context=context)
|
||||
return self.create_act_window(cr, uid, ids, context=None)
|
||||
|
||||
def button_done(self, cr, uid, ids, context=None):
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=64),
|
||||
@@ -349,22 +361,26 @@ class banking_transaction_wizard(orm.TransientModel):
|
||||
'statement_line_id', 'parent_id', type='many2one',
|
||||
relation='account.bank.statement.line', readonly=True),
|
||||
'import_transaction_id': fields.related(
|
||||
'statement_line_id', 'import_transaction_id',
|
||||
'statement_line_id', 'import_transaction_id',
|
||||
string="Import transaction",
|
||||
type='many2one', relation='banking.import.transaction'),
|
||||
'residual': fields.related(
|
||||
'import_transaction_id', 'residual', type='float',
|
||||
'import_transaction_id', 'residual', type='float',
|
||||
string='Residual', readonly=True),
|
||||
'writeoff_account_id': fields.related(
|
||||
'import_transaction_id', 'writeoff_account_id',
|
||||
type='many2one', relation='account.account',
|
||||
string='Write-off account'),
|
||||
'invoice_ids': fields.related(
|
||||
'import_transaction_id', 'invoice_ids', string="Matching invoices",
|
||||
'import_transaction_id', 'invoice_ids', string="Matching invoices",
|
||||
type='many2many', relation='account.invoice'),
|
||||
'invoice_id': fields.related(
|
||||
'import_transaction_id', 'invoice_id', string="Invoice to reconcile",
|
||||
type='many2one', relation='account.invoice'),
|
||||
'import_transaction_id',
|
||||
'invoice_id',
|
||||
string="Invoice to reconcile",
|
||||
type='many2one',
|
||||
relation='account.invoice',
|
||||
),
|
||||
'move_line_ids': fields.related(
|
||||
'import_transaction_id', 'move_line_ids', string="Entry lines",
|
||||
type='many2many', relation='account.move.line'),
|
||||
@@ -372,15 +388,20 @@ class banking_transaction_wizard(orm.TransientModel):
|
||||
'import_transaction_id', 'move_line_id', string="Entry line",
|
||||
type='many2one', relation='account.move.line'),
|
||||
'duplicate': fields.related(
|
||||
'import_transaction_id', 'duplicate', string='Flagged as duplicate',
|
||||
type='boolean'),
|
||||
'import_transaction_id',
|
||||
'duplicate',
|
||||
string='Flagged as duplicate',
|
||||
type='boolean',
|
||||
),
|
||||
'match_multi': fields.related(
|
||||
'import_transaction_id', 'match_multi',
|
||||
'import_transaction_id', 'match_multi',
|
||||
type="boolean", string='Multiple matches'),
|
||||
'match_type': fields.related(
|
||||
'import_transaction_id', 'match_type', type='selection',
|
||||
'import_transaction_id',
|
||||
'match_type',
|
||||
type='selection',
|
||||
selection=[
|
||||
('move','Move'),
|
||||
('move', 'Move'),
|
||||
('invoice', 'Invoice'),
|
||||
('payment', 'Payment line'),
|
||||
('payment_order', 'Payment order'),
|
||||
@@ -388,8 +409,10 @@ class banking_transaction_wizard(orm.TransientModel):
|
||||
('manual', 'Manual'),
|
||||
('payment_manual', 'Payment line (manual)'),
|
||||
('payment_order_manual', 'Payment order (manual)'),
|
||||
],
|
||||
string='Match type', readonly=True),
|
||||
],
|
||||
string='Match type',
|
||||
readonly=True,
|
||||
),
|
||||
'manual_invoice_ids': fields.many2many(
|
||||
'account.invoice',
|
||||
'banking_transaction_wizard_account_invoice_rel',
|
||||
@@ -401,8 +424,17 @@ class banking_transaction_wizard(orm.TransientModel):
|
||||
'wizard_id', 'move_line_id', string='Or match one or more entries',
|
||||
domain=[('account_id.reconcile', '=', True),
|
||||
('reconcile_id', '=', False)]),
|
||||
'payment_option': fields.related('import_transaction_id','payment_option', string='Payment Difference', type='selection', required=True,
|
||||
selection=[('without_writeoff', 'Keep Open'),('with_writeoff', 'Reconcile Payment Balance')]),
|
||||
'payment_option': fields.related(
|
||||
'import_transaction_id',
|
||||
'payment_option',
|
||||
string='Payment Difference',
|
||||
type='selection',
|
||||
required=True,
|
||||
selection=[
|
||||
('without_writeoff', 'Keep Open'),
|
||||
('with_writeoff', 'Reconcile Payment Balance')
|
||||
],
|
||||
),
|
||||
'writeoff_analytic_id': fields.related(
|
||||
'import_transaction_id', 'writeoff_analytic_id',
|
||||
type='many2one', relation='account.analytic.account',
|
||||
@@ -411,9 +443,11 @@ class banking_transaction_wizard(orm.TransientModel):
|
||||
'statement_line_id', 'analytic_account_id',
|
||||
type='many2one', relation='account.analytic.account',
|
||||
string="Analytic Account"),
|
||||
'move_currency_amount': fields.related('import_transaction_id','move_currency_amount',
|
||||
type='float', string='Match Currency Amount', readonly=True),
|
||||
}
|
||||
|
||||
banking_transaction_wizard()
|
||||
|
||||
'move_currency_amount': fields.related(
|
||||
'import_transaction_id',
|
||||
'move_currency_amount',
|
||||
type='float',
|
||||
string='Match Currency Amount',
|
||||
readonly=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,
|
||||
@@ -24,7 +24,7 @@ from openerp.addons.account_banking import sepa
|
||||
from openerp.addons.account_banking.struct import struct
|
||||
|
||||
__all__ = [
|
||||
'get_period',
|
||||
'get_period',
|
||||
'get_bank_accounts',
|
||||
'get_partner',
|
||||
'get_country_id',
|
||||
@@ -32,6 +32,7 @@ __all__ = [
|
||||
'create_bank_account',
|
||||
]
|
||||
|
||||
|
||||
def get_period(pool, cr, uid, date, company, log=None):
|
||||
'''
|
||||
Wrapper over account_period.find() to log exceptions of
|
||||
@@ -43,7 +44,7 @@ def get_period(pool, cr, uid, date, company, log=None):
|
||||
try:
|
||||
period_ids = pool.get('account.period').find(
|
||||
cr, uid, dt=date, context=context)
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
if log is None:
|
||||
raise
|
||||
else:
|
||||
@@ -51,6 +52,7 @@ def get_period(pool, cr, uid, date, company, log=None):
|
||||
return False
|
||||
return period_ids[0]
|
||||
|
||||
|
||||
def get_bank_accounts(pool, cr, uid, account_number, log, fail=False):
|
||||
'''
|
||||
Get the bank account with account number account_number
|
||||
@@ -72,6 +74,7 @@ def get_bank_accounts(pool, cr, uid, account_number, log, fail=False):
|
||||
return []
|
||||
return partner_bank_obj.browse(cr, uid, bank_account_ids)
|
||||
|
||||
|
||||
def _has_attr(obj, attr):
|
||||
# Needed for dangling addresses and a weird exception scheme in
|
||||
# OpenERP's orm.
|
||||
@@ -80,6 +83,7 @@ def _has_attr(obj, attr):
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
|
||||
def get_partner(pool, cr, uid, name, address, postal_code, city,
|
||||
country_id, log, context=None):
|
||||
'''
|
||||
@@ -87,7 +91,7 @@ def get_partner(pool, cr, uid, name, address, postal_code, city,
|
||||
|
||||
If multiple partners are found with the same name, select the first and
|
||||
add a warning to the import log.
|
||||
|
||||
|
||||
TODO: revive the search by lines from the address argument
|
||||
'''
|
||||
partner_obj = pool.get('res.partner')
|
||||
@@ -115,7 +119,8 @@ def get_partner(pool, cr, uid, name, address, postal_code, city,
|
||||
key = name.lower()
|
||||
partners = []
|
||||
for partner in partner_obj.read(
|
||||
cr, uid, partner_search_ids, ['name', 'commercial_partner_id'], context=context):
|
||||
cr, uid, partner_search_ids, ['name', 'commercial_partner_id'],
|
||||
context=context):
|
||||
if (len(partner['name']) > 3 and partner['name'].lower() in key):
|
||||
partners.append(partner)
|
||||
partners.sort(key=lambda x: len(x['name']), reverse=True)
|
||||
@@ -126,6 +131,7 @@ def get_partner(pool, cr, uid, name, address, postal_code, city,
|
||||
'name %(name)s') % {'name': name})
|
||||
return partner_ids and partner_ids[0] or False
|
||||
|
||||
|
||||
def get_company_bank_account(pool, cr, uid, account_number, currency,
|
||||
company, log):
|
||||
'''
|
||||
@@ -139,16 +145,16 @@ def get_company_bank_account(pool, cr, uid, account_number, currency,
|
||||
return False
|
||||
elif len(bank_accounts) != 1:
|
||||
log.append(
|
||||
_('More than one bank account was found with the same number %(account_no)s')
|
||||
% dict(account_no = account_number)
|
||||
_('More than one bank account was found with the same number '
|
||||
'%(account_no)s') % dict(account_no=account_number)
|
||||
)
|
||||
return False
|
||||
if bank_accounts[0].partner_id.id != company.partner_id.id:
|
||||
log.append(
|
||||
_('Account %(account_no)s is not owned by %(partner)s')
|
||||
% dict(account_no = account_number,
|
||||
partner = company.partner_id.name,
|
||||
))
|
||||
% dict(account_no=account_number,
|
||||
partner=company.partner_id.name,
|
||||
))
|
||||
return False
|
||||
results.account = bank_accounts[0]
|
||||
bank_settings_obj = pool.get('account.banking.account.settings')
|
||||
@@ -189,8 +195,9 @@ def get_company_bank_account(pool, cr, uid, account_number, currency,
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def get_or_create_bank(pool, cr, uid, bic, online=False, code=None,
|
||||
name=None):
|
||||
name=None, context=None):
|
||||
'''
|
||||
Find or create the bank with the provided BIC code.
|
||||
When online, the SWIFT database will be consulted in order to
|
||||
@@ -231,38 +238,41 @@ def get_or_create_bank(pool, cr, uid, bic, online=False, code=None,
|
||||
bank_id = False
|
||||
|
||||
if online:
|
||||
info, address = bank_obj.online_bank_info(cr, uid, bic, context=context)
|
||||
info, address = bank_obj.online_bank_info(
|
||||
cr, uid, bic, context=context
|
||||
)
|
||||
if info:
|
||||
bank_id = bank_obj.create(cr, uid, dict(
|
||||
code = info.code,
|
||||
name = info.name,
|
||||
street = address.street,
|
||||
street2 = address.street2,
|
||||
zip = address.zip,
|
||||
city = address.city,
|
||||
country = country_id,
|
||||
bic = info.bic[:8],
|
||||
code=info.code,
|
||||
name=info.name,
|
||||
street=address.street,
|
||||
street2=address.street2,
|
||||
zip=address.zip,
|
||||
city=address.city,
|
||||
country=country_id,
|
||||
bic=info.bic[:8],
|
||||
))
|
||||
else:
|
||||
info = struct(name=name, code=code)
|
||||
|
||||
if not online or not bank_id:
|
||||
bank_id = bank_obj.create(cr, uid, dict(
|
||||
code = info.code or 'UNKNOW',
|
||||
name = info.name or _('Unknown Bank'),
|
||||
country = country_id,
|
||||
bic = bic,
|
||||
code=info.code or 'UNKNOW', # FIXME: Typo?
|
||||
name=info.name or _('Unknown Bank'),
|
||||
country=country_id,
|
||||
bic=bic,
|
||||
))
|
||||
return bank_id, country_id
|
||||
|
||||
|
||||
def get_country_id(pool, cr, uid, transaction, context=None):
|
||||
"""
|
||||
Derive a country id from the info on the transaction.
|
||||
|
||||
|
||||
:param transaction: browse record of a transaction
|
||||
:returns: res.country id or False
|
||||
:returns: res.country id or False
|
||||
"""
|
||||
|
||||
|
||||
country_code = False
|
||||
iban = sepa.IBAN(transaction.remote_account)
|
||||
if iban.valid:
|
||||
@@ -283,6 +293,7 @@ def get_country_id(pool, cr, uid, transaction, context=None):
|
||||
country_id = company.partner_id.country.id
|
||||
return country_id
|
||||
|
||||
|
||||
def create_bank_account(pool, cr, uid, partner_id,
|
||||
account_number, holder_name, address, city,
|
||||
country_id, bic=False,
|
||||
@@ -291,9 +302,9 @@ def create_bank_account(pool, cr, uid, partner_id,
|
||||
Create a matching bank account with this holder for this partner.
|
||||
'''
|
||||
values = struct(
|
||||
partner_id = partner_id,
|
||||
owner_name = holder_name,
|
||||
country_id = country_id,
|
||||
partner_id=partner_id,
|
||||
owner_name=holder_name,
|
||||
country_id=country_id,
|
||||
)
|
||||
|
||||
# Are we dealing with IBAN?
|
||||
@@ -325,5 +336,3 @@ def create_bank_account(pool, cr, uid, partner_id,
|
||||
# Create bank account and return
|
||||
return pool.get('res.partner.bank').create(
|
||||
cr, uid, values, context=context)
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -24,6 +24,7 @@ from openerp.tools.translate import _
|
||||
from openerp.addons.account_banking.wizard import banktools
|
||||
import ast
|
||||
|
||||
|
||||
class link_partner(orm.TransientModel):
|
||||
_name = 'banking.link_partner'
|
||||
_description = 'Link partner'
|
||||
@@ -66,14 +67,14 @@ class link_partner(orm.TransientModel):
|
||||
'mobile': fields.char('Mobile', size=64),
|
||||
'is_company': fields.boolean('Is a Company'),
|
||||
}
|
||||
|
||||
|
||||
_defaults = {
|
||||
'is_company': True,
|
||||
}
|
||||
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
"""
|
||||
Get default values from the transaction data
|
||||
Get default values from the transaction data
|
||||
on the statement line
|
||||
"""
|
||||
if vals and vals.get('statement_line_id'):
|
||||
@@ -86,7 +87,7 @@ class link_partner(orm.TransientModel):
|
||||
raise orm.except_orm(
|
||||
_('Error'),
|
||||
_('Statement line is already linked to a bank account '))
|
||||
|
||||
|
||||
if not(transaction
|
||||
and transaction.remote_account):
|
||||
raise orm.except_orm(
|
||||
@@ -124,17 +125,17 @@ class link_partner(orm.TransientModel):
|
||||
|
||||
return super(link_partner, self).create(
|
||||
cr, uid, vals, context=context)
|
||||
|
||||
|
||||
def update_partner_values(self, cr, uid, wizard, values, context=None):
|
||||
"""
|
||||
Updates the new partner values with the values from the wizard
|
||||
|
||||
|
||||
:param wizard: read record of wizard (with load='_classic_write')
|
||||
:param values: the dictionary of partner values that will be updated
|
||||
"""
|
||||
for field in ['is_company',
|
||||
'name',
|
||||
'street',
|
||||
'street',
|
||||
'street2',
|
||||
'zip',
|
||||
'city',
|
||||
@@ -148,7 +149,7 @@ class link_partner(orm.TransientModel):
|
||||
if wizard[field]:
|
||||
values[field] = wizard[field]
|
||||
return True
|
||||
|
||||
|
||||
def link_partner(self, cr, uid, ids, context=None):
|
||||
statement_line_obj = self.pool.get(
|
||||
'account.bank.statement.line')
|
||||
@@ -160,13 +161,13 @@ class link_partner(orm.TransientModel):
|
||||
wiz_read = self.read(
|
||||
cr, uid, ids[0], context=context, load='_classic_write')
|
||||
partner_vals = {
|
||||
'type': 'default',
|
||||
}
|
||||
'type': 'default',
|
||||
}
|
||||
self.update_partner_values(
|
||||
cr, uid, wiz_read, partner_vals, context=context)
|
||||
partner_id = self.pool.get('res.partner').create(
|
||||
cr, uid, partner_vals, context=context)
|
||||
|
||||
|
||||
partner_bank_id = banktools.create_bank_account(
|
||||
self.pool, cr, uid, partner_id,
|
||||
wiz.remote_account, wiz.name,
|
||||
@@ -185,10 +186,10 @@ class link_partner(orm.TransientModel):
|
||||
{'partner_bank_id': partner_bank_id,
|
||||
'partner_id': partner_id}, context=context)
|
||||
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
def create_act_window(self, cr, uid, ids, nodestroy=True, context=None):
|
||||
"""
|
||||
"""
|
||||
Return a popup window for this model
|
||||
"""
|
||||
if isinstance(ids, (int, long)):
|
||||
@@ -205,5 +206,3 @@ class link_partner(orm.TransientModel):
|
||||
'res_id': ids[0],
|
||||
'nodestroy': nodestroy,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,8 +4,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,
|
||||
|
||||
@@ -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,
|
||||
@@ -26,6 +26,7 @@ from openerp.addons.account_banking.parsers.convert import str2date
|
||||
|
||||
bt = models.mem_bank_transaction
|
||||
|
||||
|
||||
class transaction(models.mem_bank_transaction):
|
||||
|
||||
def __init__(self, values, *args, **kwargs):
|
||||
@@ -36,6 +37,7 @@ class transaction(models.mem_bank_transaction):
|
||||
def is_valid(self):
|
||||
return not self.error_message
|
||||
|
||||
|
||||
class parser(models.parser):
|
||||
code = 'CAMT'
|
||||
country_code = 'NL'
|
||||
@@ -69,7 +71,7 @@ CAMT Format parser
|
||||
def find(self, node, expr):
|
||||
"""
|
||||
Like xpath(), but return first result if any or else False
|
||||
|
||||
|
||||
Return None to test nodes for being truesy
|
||||
"""
|
||||
result = node.xpath(expr, namespaces={'ns': self.ns[1:-1]})
|
||||
@@ -82,19 +84,20 @@ CAMT Format parser
|
||||
:param node: BkToCstmrStmt/Stmt/Bal node
|
||||
:param balance type: one of 'OPBD', 'PRCD', 'ITBD', 'CLBD'
|
||||
"""
|
||||
code_expr = './ns:Bal/ns:Tp/ns:CdOrPrtry/ns:Cd[text()="%s"]/../../..' % balance_type
|
||||
code_expr = ('./ns:Bal/ns:Tp/ns:CdOrPrtry/ns:Cd[text()="%s"]/../../..'
|
||||
% balance_type)
|
||||
return self.xpath(node, code_expr)
|
||||
|
||||
|
||||
def parse_amount(self, node):
|
||||
"""
|
||||
Parse an element that contains both Amount and CreditDebitIndicator
|
||||
|
||||
|
||||
:return: signed amount
|
||||
:returntype: float
|
||||
"""
|
||||
sign = -1 if node.find(self.ns + 'CdtDbtInd').text == 'DBIT' else 1
|
||||
return sign * float(node.find(self.ns + 'Amt').text)
|
||||
|
||||
|
||||
def get_start_balance(self, node):
|
||||
"""
|
||||
Find the (only) balance node with code OpeningBalance, or
|
||||
@@ -240,7 +243,7 @@ CAMT Format parser
|
||||
structured = self.find(
|
||||
TxDtls, './ns:RmtInf/ns:Strd/ns:CdtrRefInf/ns:Ref')
|
||||
if structured is None or not structured.text:
|
||||
structured = self.find(TxDtls, './ns:Refs/ns:EndToEndId')
|
||||
structured = self.find(TxDtls, './ns:Refs/ns:EndToEndId')
|
||||
if structured is not None:
|
||||
vals['reference'] = structured.text
|
||||
else:
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
# garantees and support are strongly adviced to contract EduSense BV
|
||||
#
|
||||
# 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,6 +25,5 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
import patu
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
from . import patu
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
# garantees and support are strongly adviced to contract EduSense BV
|
||||
#
|
||||
# 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,
|
||||
@@ -24,6 +24,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
{
|
||||
'name': 'Account Banking PATU module',
|
||||
'version': '0.62',
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#!/usr/bin/env python
|
||||
# encoding: utf-8
|
||||
"""Parser for PATU format files"""
|
||||
import re, datetime
|
||||
import re
|
||||
import datetime
|
||||
|
||||
|
||||
def fixchars(line):
|
||||
"""Fix the characters mangled in the input
|
||||
@@ -20,107 +22,120 @@ def fixchars(line):
|
||||
class PatuParser(object):
|
||||
"""Parse PATU lines in to structs"""
|
||||
|
||||
def __init__( self ):
|
||||
def __init__(self):
|
||||
""" Initialize PATU parser """
|
||||
|
||||
recparse = dict()
|
||||
recparse["00"] = "T(?P<recordid>00)(?P<record_len>\d{3})" \
|
||||
+ "(?P<version>\d{3})(?P<accountnr>\d{14})" \
|
||||
+ "(?P<statementnr>\d{3})(?P<startdate>\d{6})" \
|
||||
+ "(?P<enddate>\d{6})" \
|
||||
+ "(?P<creationdate>\d{6})(?P<creationtime>\d{4})" \
|
||||
+ "(?P<customerid>.{17})(?P<balancedate>\d{6})" \
|
||||
+ "(?P<startingbalance>.{19})" \
|
||||
+ "(?P<itemcount>\d{6})(?P<currency>.{3})" \
|
||||
+ "(?P<accountname>.{30})"\
|
||||
+ "(?P<accountlimit>\d{18})(?P<accountowner>.{35})" \
|
||||
+ "(?P<bankcontact1>.{40})(?P<bankcontact2>.{40})" \
|
||||
+ "(?P<bankcontact3>.{30})(?P<ibanswift>.{30})"
|
||||
recparse["10"] = "T(?P<recordid>[18]0)(?P<record_len>\d{3})" \
|
||||
+ "(?P<eventid>\d{6})" \
|
||||
+ "(?P<archivalnr>.{18})(?P<recorddate>\d{6})" \
|
||||
+ "(?P<valuedate>\d{6})" \
|
||||
+ "(?P<paymentdate>\d{6})(?P<eventtype>\d)" \
|
||||
+ "(?P<eventcode>.{3})(?P<eventdesc>.{35})" \
|
||||
+ "(?P<amount>.{19})(?P<receiptcode>.)(?P<creationmethod>.)" \
|
||||
+ "(?P<recipientname>.{35})(?P<recipientsource>.)" \
|
||||
+ "(?P<recipientaccount>.{14})(?P<recipientaccountchanged>.)" \
|
||||
+ "(?P<refnr>.{20})" \
|
||||
+ "(?P<formnr>.{8})(?P<eventlevel>.)"
|
||||
recparse["11"] = "T(?P<recordid>[18]1)(?P<record_len>\d{3})" \
|
||||
+ "(?P<infotype>.{2})" \
|
||||
+ "(?:(?# Match specific info)" \
|
||||
+ "(?<=00)(?P<message>.{35})+" \
|
||||
+ "|" \
|
||||
+ "(?<=01)(?P<transactioncount>\d{8})" \
|
||||
+ "|" \
|
||||
+ "(?<=02)(?P<customerid>.{10})\s(?P<invoicenr>.{15})\s" \
|
||||
+ "(?P<invoicedate>\d{6})" \
|
||||
+ "|" \
|
||||
+ "(?<=03)(?P<cardnumber>.{19})\s(?P<storereference>.{14})" \
|
||||
+ "|" \
|
||||
+ "(?<=04)(?P<origarchiveid>.{18})" \
|
||||
+ "|" \
|
||||
+ "(?<=05)(?P<destinationamount>.{19})\s(?P<currency>.{3})\s" \
|
||||
+ "(?P<exchangerate>.{11})(?P<rateref>.{6})" \
|
||||
+ "|" \
|
||||
+ "(?<=06)(?P<principalinfo1>.{35})(?P<principalinfo2>.{35})" \
|
||||
+ "|" \
|
||||
+ "(?<=07)(?P<bankinfo1>.{35})" \
|
||||
+ "(?P<bankinfo2>.{35})?" \
|
||||
+ "(?P<bankinfo3>.{35})?" \
|
||||
+ "(?P<bankinfo4>.{35})?" \
|
||||
+ "(?P<bankinfo5>.{35})?" \
|
||||
+ "(?P<bankinfo6>.{35})?" \
|
||||
+ "(?P<bankinfo7>.{35})?" \
|
||||
+ "(?P<bankinfo8>.{35})?" \
|
||||
+ "(?P<bankinfo9>.{35})?" \
|
||||
+ "(?P<bankinfo10>.{35})?" \
|
||||
+ "(?P<bankinfo11>.{35})?" \
|
||||
+ "(?P<bankinfo12>.{35})?" \
|
||||
+ "|" \
|
||||
+ "(?<=08)(?P<paymentcode>\d{3})\s(?P<paymentdesc>.{31})" \
|
||||
+ "|" \
|
||||
+ "(?<=09)(?P<recipientname2>.{35})" \
|
||||
+ "|" \
|
||||
+ "(?<=11)(?P<reference>.{35})(?P<recipientiban>.{35})" \
|
||||
+ "(?P<recipientbic>.{35})(?P<recipientnameiban>.{70})" \
|
||||
+ "(?P<sendername>.{70})(?P<senderid>.{35})" \
|
||||
+ "(?P<archivalid>.{70})" \
|
||||
+ ")"
|
||||
recparse["40"] = "T(?P<recordid>40)(?P<record_len>\d{3})" \
|
||||
+ "(?P<recorddate>\d{6})(?P<balance>.{19})" \
|
||||
+ "(?P<availablefunds>.{19})"
|
||||
recparse["50"] = "T(?P<recordid>50)(?P<record_len>\d{3})" \
|
||||
+ "(?P<period>\d)(?P<perioddate>\d{6})" \
|
||||
+ "(?P<depositcount>\d{8})(?P<depositsum>.{19})" \
|
||||
+ "(?P<withdrawcount>\d{8})(?P<withdrawsum>.{19})"
|
||||
recparse["60"] = "T(?P<recordid>60)(?P<record_len>\d{3})" \
|
||||
+ "(?P<bankid>.{3})(?P<specialid>01)" \
|
||||
+ "(?P<interestperiodstart>\d{6})-" \
|
||||
+ "(?P<interestperiodend>\d{6})" \
|
||||
+ "(?P<avgbalanceinfo>.)(?P<avgbalance>.{19})" \
|
||||
+ "(?P<interestinfo>.)(?P<interestrate>\d{7})" \
|
||||
+ "(?P<limitbalanceinfo>.)(?P<avglimitbalance>.{19})" \
|
||||
+ "(?P<limitinterestinfo>.)(?P<limitinterestrate>\d{7})" \
|
||||
+ "(?P<limitusageinfo>.)(?P<limitusage>\d{7})" \
|
||||
+ "(?P<permanentbalanceinfo>.)(?P<permanentbalance>.{19})" \
|
||||
+ "(?P<refinterestinfo>.)(?P<refinterestname>.{35})" \
|
||||
+ "(?P<refinterestrate>\d{7})" \
|
||||
+ "(?P<refcreditinfo>.)(?P<refcreditname>.{35})" \
|
||||
+ "(?P<refcreditrate>\d{7})"
|
||||
recparse["70"] = "T(?P<recordid>70)(?P<record_len>\d{3})" \
|
||||
+ "(?P<bankid>\d{3})" \
|
||||
+ "(?P<infoline1>.{80})" \
|
||||
+ "(?P<infoline2>.{80})?" \
|
||||
+ "(?P<infoline3>.{80})?" \
|
||||
+ "(?P<infoline4>.{80})?" \
|
||||
+ "(?P<infoline5>.{80})?" \
|
||||
+ "(?P<infoline6>.{80})?"
|
||||
recparse["00"] = (
|
||||
"T(?P<recordid>00)(?P<record_len>\d{3})"
|
||||
"(?P<version>\d{3})(?P<accountnr>\d{14})"
|
||||
"(?P<statementnr>\d{3})(?P<startdate>\d{6})"
|
||||
"(?P<enddate>\d{6})"
|
||||
"(?P<creationdate>\d{6})(?P<creationtime>\d{4})"
|
||||
"(?P<customerid>.{17})(?P<balancedate>\d{6})"
|
||||
"(?P<startingbalance>.{19})"
|
||||
"(?P<itemcount>\d{6})(?P<currency>.{3})"
|
||||
"(?P<accountname>.{30})"
|
||||
"(?P<accountlimit>\d{18})(?P<accountowner>.{35})"
|
||||
"(?P<bankcontact1>.{40})(?P<bankcontact2>.{40})"
|
||||
"(?P<bankcontact3>.{30})(?P<ibanswift>.{30})"
|
||||
)
|
||||
recparse["10"] = (
|
||||
"T(?P<recordid>[18]0)(?P<record_len>\d{3})"
|
||||
"(?P<eventid>\d{6})"
|
||||
"(?P<archivalnr>.{18})(?P<recorddate>\d{6})"
|
||||
"(?P<valuedate>\d{6})"
|
||||
"(?P<paymentdate>\d{6})(?P<eventtype>\d)"
|
||||
"(?P<eventcode>.{3})(?P<eventdesc>.{35})"
|
||||
"(?P<amount>.{19})(?P<receiptcode>.)(?P<creationmethod>.)"
|
||||
"(?P<recipientname>.{35})(?P<recipientsource>.)"
|
||||
"(?P<recipientaccount>.{14})(?P<recipientaccountchanged>.)"
|
||||
"(?P<refnr>.{20})"
|
||||
"(?P<formnr>.{8})(?P<eventlevel>.)"
|
||||
)
|
||||
recparse["11"] = (
|
||||
"T(?P<recordid>[18]1)(?P<record_len>\d{3})"
|
||||
"(?P<infotype>.{2})"
|
||||
"(?:(?# Match specific info)"
|
||||
"(?<=00)(?P<message>.{35})+"
|
||||
"|"
|
||||
"(?<=01)(?P<transactioncount>\d{8})"
|
||||
"|"
|
||||
"(?<=02)(?P<customerid>.{10})\s(?P<invoicenr>.{15})\s"
|
||||
"(?P<invoicedate>\d{6})"
|
||||
"|"
|
||||
"(?<=03)(?P<cardnumber>.{19})\s(?P<storereference>.{14})"
|
||||
"|"
|
||||
"(?<=04)(?P<origarchiveid>.{18})"
|
||||
"|"
|
||||
"(?<=05)(?P<destinationamount>.{19})\s(?P<currency>.{3})\s"
|
||||
"(?P<exchangerate>.{11})(?P<rateref>.{6})"
|
||||
"|"
|
||||
"(?<=06)(?P<principalinfo1>.{35})(?P<principalinfo2>.{35})"
|
||||
"|"
|
||||
"(?<=07)(?P<bankinfo1>.{35})"
|
||||
"(?P<bankinfo2>.{35})?"
|
||||
"(?P<bankinfo3>.{35})?"
|
||||
"(?P<bankinfo4>.{35})?"
|
||||
"(?P<bankinfo5>.{35})?"
|
||||
"(?P<bankinfo6>.{35})?"
|
||||
"(?P<bankinfo7>.{35})?"
|
||||
"(?P<bankinfo8>.{35})?"
|
||||
"(?P<bankinfo9>.{35})?"
|
||||
"(?P<bankinfo10>.{35})?"
|
||||
"(?P<bankinfo11>.{35})?"
|
||||
"(?P<bankinfo12>.{35})?"
|
||||
"|"
|
||||
"(?<=08)(?P<paymentcode>\d{3})\s(?P<paymentdesc>.{31})"
|
||||
"|"
|
||||
"(?<=09)(?P<recipientname2>.{35})"
|
||||
"|"
|
||||
"(?<=11)(?P<reference>.{35})(?P<recipientiban>.{35})"
|
||||
"(?P<recipientbic>.{35})(?P<recipientnameiban>.{70})"
|
||||
"(?P<sendername>.{70})(?P<senderid>.{35})"
|
||||
"(?P<archivalid>.{70})"
|
||||
")"
|
||||
)
|
||||
recparse["40"] = (
|
||||
"T(?P<recordid>40)(?P<record_len>\d{3})"
|
||||
"(?P<recorddate>\d{6})(?P<balance>.{19})"
|
||||
"(?P<availablefunds>.{19})"
|
||||
)
|
||||
recparse["50"] = (
|
||||
"T(?P<recordid>50)(?P<record_len>\d{3})"
|
||||
"(?P<period>\d)(?P<perioddate>\d{6})"
|
||||
"(?P<depositcount>\d{8})(?P<depositsum>.{19})"
|
||||
"(?P<withdrawcount>\d{8})(?P<withdrawsum>.{19})"
|
||||
)
|
||||
recparse["60"] = (
|
||||
"T(?P<recordid>60)(?P<record_len>\d{3})"
|
||||
"(?P<bankid>.{3})(?P<specialid>01)"
|
||||
"(?P<interestperiodstart>\d{6})-"
|
||||
"(?P<interestperiodend>\d{6})"
|
||||
"(?P<avgbalanceinfo>.)(?P<avgbalance>.{19})"
|
||||
"(?P<interestinfo>.)(?P<interestrate>\d{7})"
|
||||
"(?P<limitbalanceinfo>.)(?P<avglimitbalance>.{19})"
|
||||
"(?P<limitinterestinfo>.)(?P<limitinterestrate>\d{7})"
|
||||
"(?P<limitusageinfo>.)(?P<limitusage>\d{7})"
|
||||
"(?P<permanentbalanceinfo>.)(?P<permanentbalance>.{19})"
|
||||
"(?P<refinterestinfo>.)(?P<refinterestname>.{35})"
|
||||
"(?P<refinterestrate>\d{7})"
|
||||
"(?P<refcreditinfo>.)(?P<refcreditname>.{35})"
|
||||
"(?P<refcreditrate>\d{7})"
|
||||
)
|
||||
recparse["70"] = (
|
||||
"T(?P<recordid>70)(?P<record_len>\d{3})"
|
||||
"(?P<bankid>\d{3})"
|
||||
"(?P<infoline1>.{80})"
|
||||
"(?P<infoline2>.{80})?"
|
||||
"(?P<infoline3>.{80})?"
|
||||
"(?P<infoline4>.{80})?"
|
||||
"(?P<infoline5>.{80})?"
|
||||
"(?P<infoline6>.{80})?"
|
||||
)
|
||||
for record in recparse:
|
||||
recparse[record] = re.compile(recparse[record])
|
||||
self.recparse = recparse
|
||||
|
||||
|
||||
def parse_record(self, line):
|
||||
"""Docstring for parse_perus
|
||||
@@ -135,7 +150,7 @@ class PatuParser(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()
|
||||
@@ -146,7 +161,8 @@ class PatuParser(object):
|
||||
del matchdict[field]
|
||||
|
||||
matchkeys = set(matchdict.keys())
|
||||
needstrip = set(["bankcontact1", "bankcontact2", "bankcontact3",
|
||||
needstrip = set([
|
||||
"bankcontact1", "bankcontact2", "bankcontact3",
|
||||
"customerid", "accountowner", "accountname", "refnr", "formnr",
|
||||
"recipientname", "eventdesc", "recipientaccount", "message",
|
||||
"principalinfo1", "bankinfo1", "bankinfo2", "bankinfo3",
|
||||
@@ -158,30 +174,35 @@ class PatuParser(object):
|
||||
for field in matchkeys & needstrip:
|
||||
matchdict[field] = matchdict[field].strip()
|
||||
# Convert to int
|
||||
needsint = set(["itemcount", "eventid", "record_len",
|
||||
needsint = set([
|
||||
"itemcount", "eventid", "record_len",
|
||||
"depositcount", "withdrawcount"])
|
||||
for field in matchkeys & needsint:
|
||||
matchdict[field] = float(matchdict[field])
|
||||
# Convert to float
|
||||
needsfloat = set(["startingbalance", "accountlimit", "amount",
|
||||
needsfloat = set([
|
||||
"startingbalance", "accountlimit", "amount",
|
||||
"destinationamount", "balance", "availablefunds", "depositsum",
|
||||
"withdrawsum", "avgbalance", "avglimitbalance",
|
||||
"permanentbalance"])
|
||||
for field in matchkeys & needsfloat:
|
||||
matchdict[field] = float(matchdict[field])
|
||||
# convert sents to euros
|
||||
needseur = set(["startingbalance", "accountlimit", "amount",
|
||||
needseur = set([
|
||||
"startingbalance", "accountlimit", "amount",
|
||||
"destinationamount", "balance", "availablefunds", "depositsum",
|
||||
"withdrawsum", "avgbalance", "permanentbalance"])
|
||||
for field in matchkeys & needseur:
|
||||
matchdict[field] = matchdict[field] / 100
|
||||
# convert ibanswift to separate fields
|
||||
if matchdict.has_key("ibanswift"):
|
||||
matchdict["iban"], matchdict["swift"] = \
|
||||
matchdict["ibanswift"].strip().split()
|
||||
if "ibanswift" in matchdict:
|
||||
matchdict["iban"], matchdict["swift"] = (
|
||||
matchdict["ibanswift"].strip().split()
|
||||
)
|
||||
|
||||
# Convert date fields
|
||||
needdate = set(["startdate", "enddate", "creationdate", "balancedate",
|
||||
needdate = set([
|
||||
"startdate", "enddate", "creationdate", "balancedate",
|
||||
"valuedate", "paymentdate", "recorddate", "perioddate"])
|
||||
for field in matchkeys & needdate:
|
||||
# Base all dates on the year 2000, since it's unlikely that this
|
||||
@@ -191,17 +212,20 @@ class PatuParser(object):
|
||||
matchdict[field] = None
|
||||
continue
|
||||
|
||||
matchdict[field] = datetime.date(int("20" + datestring[0:2]),
|
||||
int(datestring[2:4]), int(datestring[4:6]))
|
||||
matchdict[field] = datetime.date(
|
||||
int("20" + datestring[0:2]),
|
||||
int(datestring[2:4]), int(datestring[4:6]))
|
||||
# convert time fields
|
||||
needtime = set(["creationtime"])
|
||||
for field in matchkeys & needtime:
|
||||
timestring = matchdict[field]
|
||||
matchdict[field] = datetime.time(int(timestring[0:2]),
|
||||
int(timestring[2:4]))
|
||||
matchdict[field] = datetime.time(
|
||||
int(timestring[0:2]),
|
||||
int(timestring[2:4]))
|
||||
|
||||
return matchdict
|
||||
|
||||
|
||||
def parse_file(filename):
|
||||
"""Parse file with PATU format inside
|
||||
|
||||
@@ -214,6 +238,7 @@ def parse_file(filename):
|
||||
for line in patufile:
|
||||
parser.parse_record(line)
|
||||
|
||||
|
||||
def main():
|
||||
"""The main function, currently just calls a dummy filename
|
||||
|
||||
@@ -223,5 +248,3 @@ def main():
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -30,20 +30,23 @@ from account_banking_fi_patu.parser import PatuParser
|
||||
|
||||
__all__ = ['parser']
|
||||
|
||||
|
||||
class transaction(models.mem_bank_transaction):
|
||||
'''
|
||||
Implementation of transaction communication class for account_banking.
|
||||
'''
|
||||
mapping = {
|
||||
"remote_account": "recipientaccount",
|
||||
"remote_currency": "currency",
|
||||
"transferred_amount": "amount",
|
||||
"execution_date": "recorddate",
|
||||
"value_date": "paymentdate",
|
||||
"transfer_type": "eventtype",
|
||||
"reference": "refnr",
|
||||
"eventcode": "eventcode",
|
||||
"message": "message"}
|
||||
"remote_account": "recipientaccount",
|
||||
"remote_currency": "currency",
|
||||
"transferred_amount": "amount",
|
||||
"execution_date": "recorddate",
|
||||
"value_date": "paymentdate",
|
||||
"transfer_type": "eventtype",
|
||||
"reference": "refnr",
|
||||
"eventcode": "eventcode",
|
||||
"message": "message"
|
||||
}
|
||||
|
||||
def __init__(self, record, *args, **kwargs):
|
||||
'''
|
||||
Initialize own dict with read values.
|
||||
@@ -63,19 +66,19 @@ class transaction(models.mem_bank_transaction):
|
||||
If eventcode is 730, the transaction was initiated by the bank and
|
||||
doesn't have a destination account.
|
||||
'''
|
||||
if self.eventcode and (self.eventcode == "720" or self.eventcode ==
|
||||
"710"):
|
||||
if self.eventcode in ["720", "710"]:
|
||||
# Withdrawal from and deposit to the account
|
||||
return (self.execution_date and self.transferred_amount and True) \
|
||||
or False
|
||||
or False
|
||||
|
||||
if self.eventcode and self.eventcode == "730":
|
||||
# The transaction is bank initiated, no remote account is present
|
||||
return (self.execution_date and self.transferred_amount and True) \
|
||||
or False
|
||||
or False
|
||||
|
||||
return super(transaction, self).is_valid()
|
||||
|
||||
|
||||
class statement(models.mem_bank_statement):
|
||||
'''
|
||||
Implementation of bank_statement communication class of account_banking
|
||||
@@ -110,6 +113,7 @@ class statement(models.mem_bank_statement):
|
||||
return
|
||||
self.transactions.append(transaction(record))
|
||||
|
||||
|
||||
class parser(models.parser):
|
||||
code = 'FIPATU'
|
||||
name = _('PATU statement sheet')
|
||||
|
||||
@@ -43,11 +43,19 @@
|
||||
French Letter of Change
|
||||
=======================
|
||||
|
||||
This module adds support for French Letters of Change (in French : Lettre de Change Relevé aka LCR). This payment type is still in use in France and it is *not* replaced by SEPA one-off Direct Debits. With this module, you can generate a CFONB file to send to your bank.
|
||||
This module adds support for French Letters of Change (in French :
|
||||
Lettre de Change Relevé aka LCR).
|
||||
|
||||
This module uses the framework provided by the banking addons, cf https://launchpad.net/banking-addons
|
||||
This payment type is still in use in France and it is *not* replaced by SEPA
|
||||
one-off Direct Debits.
|
||||
|
||||
Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com> for any help or question about this module.
|
||||
''',
|
||||
With this module, you can generate a CFONB file to send to your bank.
|
||||
|
||||
This module uses the framework provided by the banking addons,
|
||||
cf https://github.com/OCA/banking
|
||||
|
||||
Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com>
|
||||
for any help or question about this module.
|
||||
''',
|
||||
'active': False,
|
||||
}
|
||||
|
||||
@@ -23,10 +23,14 @@ This module provides online bank databases for conversion between BBAN and
|
||||
IBAN numbers and for consulting.
|
||||
'''
|
||||
import re
|
||||
import urllib, urllib2
|
||||
import urllib
|
||||
import urllib2
|
||||
from BeautifulSoup import BeautifulSoup
|
||||
from openerp.addons.account_banking.sepa import postalcode
|
||||
from openerp.addons.account_banking_iban_lookup.urlagent import URLAgent, SoupForm
|
||||
from openerp.addons.account_banking_iban_lookup.urlagent import (
|
||||
URLAgent,
|
||||
SoupForm,
|
||||
)
|
||||
from openerp.addons.account_banking.sepa.iban import IBAN
|
||||
from openerp.addons.account_banking.struct import struct
|
||||
|
||||
@@ -38,6 +42,7 @@ __all__ = [
|
||||
IBANlink_NL = 'http://www.ibannl.org/iban_check.php'
|
||||
IBANlink_BE = 'http://www.ibanbic.be/'
|
||||
|
||||
|
||||
def get_iban_bic_NL(bank_acc):
|
||||
'''
|
||||
Consult the Dutch online banking database to check both the account number
|
||||
@@ -51,14 +56,14 @@ def get_iban_bic_NL(bank_acc):
|
||||
if len(number) <= 7:
|
||||
iban = IBAN.create(BBAN='INGB' + number.rjust(10, '0'),
|
||||
countrycode='NL'
|
||||
)
|
||||
)
|
||||
return struct(
|
||||
iban = iban.replace(' ',''),
|
||||
account = iban.BBAN[4:],
|
||||
bic = 'INGBNL2A',
|
||||
code = 'INGBNL',
|
||||
bank = 'ING Bank N.V.',
|
||||
country_id = 'NL',
|
||||
iban=iban.replace(' ', ''),
|
||||
account=iban.BBAN[4:],
|
||||
bic='INGBNL2A',
|
||||
code='INGBNL',
|
||||
bank='ING Bank N.V.',
|
||||
country_id='NL',
|
||||
)
|
||||
|
||||
data = urllib.urlencode(dict(number=number, method='POST'))
|
||||
@@ -66,6 +71,7 @@ def get_iban_bic_NL(bank_acc):
|
||||
response = urllib2.urlopen(request)
|
||||
soup = BeautifulSoup(response)
|
||||
result = struct()
|
||||
attr = None
|
||||
for _pass, td in enumerate(soup.findAll('td')):
|
||||
if _pass % 2 == 1:
|
||||
result[attr] = unicode(td.find('font').contents[0])
|
||||
@@ -81,6 +87,7 @@ def get_iban_bic_NL(bank_acc):
|
||||
return result
|
||||
return None
|
||||
|
||||
|
||||
def get_iban_bic_BE(bank_acc):
|
||||
'''
|
||||
Consult the Belgian online database to check both account number and the
|
||||
@@ -88,7 +95,9 @@ def get_iban_bic_BE(bank_acc):
|
||||
in Belgium and will only convert Belgian local account numbers.
|
||||
'''
|
||||
def contents(soup, attr):
|
||||
return soup.find('input', {'id': 'textbox%s' % attr}).get('value').strip()
|
||||
return soup.find('input', {
|
||||
'id': 'textbox%s' % attr
|
||||
}).get('value').strip()
|
||||
|
||||
if not bank_acc.strip():
|
||||
return None
|
||||
@@ -121,6 +130,7 @@ def get_iban_bic_BE(bank_acc):
|
||||
result.code = result.bic[:6]
|
||||
return result
|
||||
|
||||
|
||||
def BBAN_is_IBAN(bank_acc):
|
||||
'''
|
||||
Intelligent copy, valid for SEPA members who switched to SEPA from old
|
||||
@@ -131,15 +141,16 @@ def BBAN_is_IBAN(bank_acc):
|
||||
else:
|
||||
iban_acc = IBAN(bank_acc)
|
||||
return struct(
|
||||
iban = str(iban_acc),
|
||||
account = str(bank_acc),
|
||||
country_id = iban_acc.countrycode,
|
||||
code = iban_acc.BIC_searchkey,
|
||||
iban=str(iban_acc),
|
||||
account=str(bank_acc),
|
||||
country_id=iban_acc.countrycode,
|
||||
code=iban_acc.BIC_searchkey,
|
||||
# Note: BIC can not be constructed here!
|
||||
bic = False,
|
||||
bank = False,
|
||||
bic=False,
|
||||
bank=False,
|
||||
)
|
||||
|
||||
|
||||
_account_info = {
|
||||
# TODO: Add more online data banks
|
||||
'BA': BBAN_is_IBAN,
|
||||
@@ -153,6 +164,7 @@ _account_info = {
|
||||
'SM': BBAN_is_IBAN,
|
||||
}
|
||||
|
||||
|
||||
def account_info(iso, bank_acc):
|
||||
'''
|
||||
Consult the online database for this country to obtain its
|
||||
@@ -165,9 +177,11 @@ def account_info(iso, bank_acc):
|
||||
return _account_info[iso](bank_acc)
|
||||
return False
|
||||
|
||||
|
||||
bic_re = re.compile("[^']+'([^']*)'.*")
|
||||
SWIFTlink = 'http://www.swift.com/bsl/freequery.do'
|
||||
|
||||
|
||||
def bank_info(bic):
|
||||
'''
|
||||
Consult the free online SWIFT service to obtain the name and address of a
|
||||
@@ -177,7 +191,7 @@ def bank_info(bic):
|
||||
automated usage, so user like behavior is required.
|
||||
|
||||
Update January 2012: Always return None, as the SWIFT page to retrieve the
|
||||
information does no longer exist.
|
||||
information does no longer exist.
|
||||
If demand exists, maybe bite the bullet and integrate with a paid web
|
||||
service such as http://www.iban-rechner.de.
|
||||
lp914922 additionally suggests to make online lookup optional.
|
||||
@@ -190,7 +204,7 @@ def bank_info(bic):
|
||||
for trsoup in soup('tr'):
|
||||
for stage, tdsoup in enumerate(trsoup('td')):
|
||||
if stage == 0:
|
||||
attr = tdsoup.contents[0].strip().replace(' ','_')
|
||||
attr = tdsoup.contents[0].strip().replace(' ', '_')
|
||||
elif stage == 2:
|
||||
if tdsoup.contents:
|
||||
retval[attr] = tdsoup.contents[0].strip()
|
||||
@@ -203,8 +217,8 @@ def bank_info(bic):
|
||||
request = agent.open(SWIFTlink)
|
||||
soup = BeautifulSoup(request)
|
||||
|
||||
# Parse request form. As this form is intertwined with a table, use the parent
|
||||
# as root to search for form elements.
|
||||
# Parse request form. As this form is intertwined with a table, use the
|
||||
# parent as root to search for form elements.
|
||||
form = SoupForm(soup.find('form', {'id': 'frmFreeSearch1'}), parent=True)
|
||||
|
||||
# Fill form fields
|
||||
@@ -221,7 +235,8 @@ def bank_info(bic):
|
||||
if not bic_button:
|
||||
return None, None
|
||||
|
||||
# Overwrite the location with 'any' ('XXX') to narrow the results to one or less.
|
||||
# Overwrite the location with 'any' ('XXX') to narrow the results to one
|
||||
# or less.
|
||||
# Assume this regexp will never fail...
|
||||
full_bic = bic_re.match(bic_button.get('href')).groups()[0][:8] + 'XXX'
|
||||
|
||||
@@ -236,13 +251,13 @@ def bank_info(bic):
|
||||
soup = BeautifulSoup(response)
|
||||
|
||||
# Now parse the results
|
||||
tables = soup.find('div', {'id':'Middle'}).findAll('table')
|
||||
tables = soup.find('div', {'id': 'Middle'}).findAll('table')
|
||||
if not tables:
|
||||
return None, None
|
||||
tablesoup = tables[2]('table')
|
||||
if not tablesoup:
|
||||
return None, None
|
||||
|
||||
|
||||
codes = harvest(tablesoup[0])
|
||||
if not codes:
|
||||
return None, None
|
||||
@@ -253,9 +268,9 @@ def bank_info(bic):
|
||||
# banks world wide using the same name.
|
||||
# The concatenation with the two character country code is for most
|
||||
# national branches sufficient as a unique identifier.
|
||||
code = full_bic[:6],
|
||||
bic = full_bic,
|
||||
name = codes.Institution_name,
|
||||
code=full_bic[:6],
|
||||
bic=full_bic,
|
||||
name=codes.Institution_name,
|
||||
)
|
||||
|
||||
address = harvest(tablesoup[1])
|
||||
@@ -264,14 +279,14 @@ def bank_info(bic):
|
||||
if not address.Zip_Code:
|
||||
if address.Location:
|
||||
iso, address.Zip_Code, address.Location = \
|
||||
postalcode.split(address.Location, full_bic[4:6])
|
||||
postalcode.split(address.Location, full_bic[4:6])
|
||||
|
||||
bankaddress = struct(
|
||||
street = address.Address.title(),
|
||||
city = address.Location.strip().title(),
|
||||
zip = address.Zip_Code,
|
||||
country = address.Country.title(),
|
||||
country_id = full_bic[4:6],
|
||||
street=address.Address.title(),
|
||||
city=address.Location.strip().title(),
|
||||
zip=address.Zip_Code,
|
||||
country=address.Country.title(),
|
||||
country_id=full_bic[4:6],
|
||||
)
|
||||
if ' ' in bankaddress.street:
|
||||
bankaddress.street, bankaddress.street2 = [
|
||||
@@ -281,4 +296,3 @@ def bank_info(bic):
|
||||
bankaddress.street2 = ''
|
||||
|
||||
return bankinfo, bankaddress
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -28,7 +28,8 @@ import urllib
|
||||
|
||||
__all__ = ['urlsplit', 'urljoin', 'pathbase', 'urlbase', 'SoupForm',
|
||||
'URLAgent'
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
def urlsplit(url):
|
||||
'''
|
||||
@@ -43,7 +44,8 @@ def urlsplit(url):
|
||||
host, path = urllib.splithost(url)
|
||||
return (scheme, host, path)
|
||||
|
||||
def urljoin(scheme, host, path, args = None):
|
||||
|
||||
def urljoin(scheme, host, path, args=None):
|
||||
'''
|
||||
Join scheme, host and path to a full URL.
|
||||
Optional: add urlencoded args.
|
||||
@@ -54,15 +56,17 @@ def urljoin(scheme, host, path, args = None):
|
||||
url += '?%s' % urllib.urlencode(args)
|
||||
return url
|
||||
|
||||
|
||||
def pathbase(path):
|
||||
'''
|
||||
Return the base for the path in order to satisfy relative paths.
|
||||
Helper function.
|
||||
'''
|
||||
if path and '/' in path:
|
||||
return path[:path.rfind('/') +1]
|
||||
return path[:path.rfind('/') + 1]
|
||||
return path
|
||||
|
||||
|
||||
def urlbase(url):
|
||||
'''
|
||||
Return the base URL for url in order to satisfy relative paths.
|
||||
@@ -71,6 +75,7 @@ def urlbase(url):
|
||||
scheme, host, path = urlsplit(url)
|
||||
return urljoin(scheme, host, pathbase(path))
|
||||
|
||||
|
||||
class SoupForm(object):
|
||||
'''
|
||||
A SoupForm is a representation of a HTML Form in BeautifulSoup terms.
|
||||
@@ -94,7 +99,7 @@ class SoupForm(object):
|
||||
if parent:
|
||||
self.soup = soup.parent
|
||||
|
||||
# Harvest input elements.
|
||||
# Harvest input elements.
|
||||
self._args = {}
|
||||
for item in self.soup.findAll('input'):
|
||||
# Make sure to initialize to '' to avoid None strings to appear
|
||||
@@ -150,6 +155,7 @@ class SoupForm(object):
|
||||
args.update(self._extra_args)
|
||||
return args
|
||||
|
||||
|
||||
class URLAgent(object):
|
||||
'''
|
||||
Assistent object to ease HTTP(S) requests.
|
||||
@@ -160,8 +166,12 @@ class URLAgent(object):
|
||||
super(URLAgent, self).__init__(*args, **kwargs)
|
||||
self._extra_headers = {}
|
||||
self.headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (X11; U; Linux x86_64; us; rv:1.9.0.10) Gecko/2009042708 Fedora/3.0.10-1.fc9 Firefox/3.0.10',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'User-Agent': (
|
||||
'Mozilla/5.0 (X11; U; Linux x86_64; us; rv:1.9.0.10) '
|
||||
'Gecko/2009042708 Fedora/3.0.10-1.fc9 Firefox/3.0.10'),
|
||||
'Accept': (
|
||||
'text/html,application/xhtml+xml,application/xml;'
|
||||
'q=0.9,*/*;q=0.8'),
|
||||
'Accept-Language': 'en-us;q=1.0',
|
||||
'Accept-Charset': 'UTF-8,*',
|
||||
'Cache-Control': 'max-age=0'
|
||||
@@ -193,7 +203,7 @@ class URLAgent(object):
|
||||
|
||||
# Get and set cookies for next actions
|
||||
attributes = request.info()
|
||||
if attributes.has_key('set-cookie'):
|
||||
if 'set-cookie' in attributes:
|
||||
self.agent.addheader('Cookie', attributes['set-cookie'])
|
||||
|
||||
# Add referer
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
#
|
||||
##############################################################################
|
||||
{
|
||||
"name" : "MT940",
|
||||
"version" : "1.0",
|
||||
"author" : "Therp BV",
|
||||
"name": "MT940",
|
||||
"version": "1.0",
|
||||
"author": "Therp BV",
|
||||
"complexity": "expert",
|
||||
"description": """
|
||||
This addon provides a generic parser for MT940 files. Given that MT940 is a
|
||||
@@ -32,11 +32,11 @@ certain bank.
|
||||
|
||||
See account_banking_nl_ing_mt940 for an example on how to use it.
|
||||
""",
|
||||
"category" : "Dependency",
|
||||
"depends" : [
|
||||
"category": "Dependency",
|
||||
"depends": [
|
||||
'account_banking',
|
||||
],
|
||||
"data" : [
|
||||
"data": [
|
||||
],
|
||||
"js": [
|
||||
],
|
||||
@@ -47,7 +47,7 @@ See account_banking_nl_ing_mt940 for an example on how to use it.
|
||||
"auto_install": False,
|
||||
"installable": True,
|
||||
"application": False,
|
||||
"external_dependencies" : {
|
||||
'python' : [],
|
||||
"external_dependencies": {
|
||||
'python': [],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -27,18 +27,23 @@ import re
|
||||
import datetime
|
||||
import logging
|
||||
try:
|
||||
from openerp.addons.account_banking.parsers.models import\
|
||||
mem_bank_statement, mem_bank_transaction
|
||||
from openerp.addons.account_banking.parsers.models import (
|
||||
mem_bank_statement,
|
||||
mem_bank_transaction,
|
||||
)
|
||||
from openerp.tools.misc import DEFAULT_SERVER_DATE_FORMAT
|
||||
except ImportError:
|
||||
#this allows us to run this file standalone, see __main__ at the end
|
||||
# this allows us to run this file standalone, see __main__ at the end
|
||||
|
||||
class mem_bank_statement:
|
||||
def __init__(self):
|
||||
self.transactions = []
|
||||
|
||||
class mem_bank_transaction:
|
||||
pass
|
||||
DEFAULT_SERVER_DATE_FORMAT = "%Y-%m-%d"
|
||||
|
||||
|
||||
class MT940(object):
|
||||
'''Inherit this class in your account_banking.parsers.models.parser,
|
||||
define functions to handle the tags you need to handle and adjust static
|
||||
@@ -46,7 +51,7 @@ class MT940(object):
|
||||
|
||||
Note that order matters: You need to do your_parser(MT940, parser), not the
|
||||
other way around!
|
||||
|
||||
|
||||
At least, you should override handle_tag_61 and handle_tag_86. Don't forget
|
||||
to call super.
|
||||
handle_tag_* functions receive the remainder of the the line (that is,
|
||||
@@ -59,7 +64,7 @@ class MT940(object):
|
||||
footer_regex = '^-}$'
|
||||
footer_regex = '^-XXX$'
|
||||
'The line that denotes end of message, we need to create a new statement'
|
||||
|
||||
|
||||
tag_regex = '^:[0-9]{2}[A-Z]*:'
|
||||
'The beginning of a record, should be anchored to beginning of the line'
|
||||
|
||||
@@ -194,15 +199,17 @@ class MT940(object):
|
||||
banks occur'''
|
||||
pass
|
||||
|
||||
'utility functions'
|
||||
|
||||
def str2date(string, fmt='%y%m%d'):
|
||||
return datetime.datetime.strptime(string, fmt)
|
||||
|
||||
|
||||
def str2float(string):
|
||||
return float(string.replace(',', '.'))
|
||||
|
||||
'testing'
|
||||
|
||||
def main(filename):
|
||||
"""testing"""
|
||||
parser = MT940()
|
||||
parser.parse(None, open(filename, 'r').read())
|
||||
for statement in parser.statements:
|
||||
|
||||
@@ -35,8 +35,8 @@ No formal specifications of the file layout are released by abnamro. You can
|
||||
help improve the performance of this import filter on
|
||||
https://launchpad.net/account-banking.
|
||||
|
||||
Imported bank transfers are organized in statements covering periods of one week,
|
||||
even if the imported files cover a different period.
|
||||
''',
|
||||
Imported bank transfers are organized in statements covering periods of one
|
||||
week, even if the imported files cover a different period.
|
||||
''',
|
||||
'installable': True,
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ Bank Statements along with Bank Transactions.
|
||||
'''
|
||||
from openerp.addons.account_banking.parsers import models
|
||||
from openerp.addons.account_banking.parsers.convert import str2date
|
||||
from openerp.addons.account_banking.sepa import postalcode
|
||||
from openerp.tools.translate import _
|
||||
from openerp.osv import orm
|
||||
|
||||
@@ -42,6 +41,7 @@ __all__ = ['parser']
|
||||
|
||||
bt = models.mem_bank_transaction
|
||||
|
||||
|
||||
class transaction_message(object):
|
||||
'''
|
||||
A auxiliary class to validate and coerce read values
|
||||
@@ -56,9 +56,10 @@ class transaction_message(object):
|
||||
Initialize own dict with attributes and coerce values to right type
|
||||
'''
|
||||
if len(self.attrnames) != len(values):
|
||||
raise ValueError, \
|
||||
_('Invalid transaction line: expected %d columns, found '
|
||||
'%d') % (len(self.attrnames), len(values))
|
||||
raise ValueError(
|
||||
_('Invalid transaction line: expected %d columns, found '
|
||||
'%d') % (len(self.attrnames), len(values))
|
||||
)
|
||||
''' Strip all values except the blob '''
|
||||
for (key, val) in zip(self.attrnames, values):
|
||||
self.__dict__[key] = key == 'blob' and val or val.strip()
|
||||
@@ -72,23 +73,24 @@ class transaction_message(object):
|
||||
self.statement_id = self.execution_date.strftime('%Yw%W')
|
||||
self.id = str(subno).zfill(4)
|
||||
|
||||
|
||||
class transaction(models.mem_bank_transaction):
|
||||
'''
|
||||
Implementation of transaction communication class for account_banking.
|
||||
'''
|
||||
attrnames = ['local_account', 'local_currency', 'transferred_amount',
|
||||
'blob', 'execution_date', 'value_date', 'id',
|
||||
]
|
||||
]
|
||||
|
||||
type_map = {
|
||||
# retrieved from online help in the Triodos banking application
|
||||
'BEA': bt.PAYMENT_TERMINAL, # Pin
|
||||
'GEA': bt.BANK_TERMINAL, # ATM
|
||||
'BEA': bt.PAYMENT_TERMINAL, # Pin
|
||||
'GEA': bt.BANK_TERMINAL, # ATM
|
||||
'COSTS': bt.BANK_COSTS,
|
||||
'BANK': bt.ORDER,
|
||||
'GIRO': bt.ORDER,
|
||||
'INTL': bt.ORDER, # international order
|
||||
'UNKN': bt.ORDER, # everything else
|
||||
'INTL': bt.ORDER, # international order
|
||||
'UNKN': bt.ORDER, # everything else
|
||||
'SEPA': bt.ORDER,
|
||||
'PAYB': bt.PAYMENT_BATCH,
|
||||
'RETR': bt.STORNO,
|
||||
@@ -118,10 +120,10 @@ class transaction(models.mem_bank_transaction):
|
||||
elif not self.execution_date:
|
||||
self.error_message = "No execution date"
|
||||
elif not self.remote_account and self.transfer_type not in [
|
||||
'BEA', 'GEA', 'COSTS', 'UNKN', 'PAYB',
|
||||
]:
|
||||
self.error_message = _('No remote account for transaction type '
|
||||
'%s') % self.transfer_type
|
||||
'BEA', 'GEA', 'COSTS', 'UNKN', 'PAYB', ]:
|
||||
self.error_message = _(
|
||||
'No remote account for transaction type %s'
|
||||
) % self.transfer_type
|
||||
if self.error_message:
|
||||
raise orm.except_orm(_('Error !'), _(self.error_message))
|
||||
return not self.error_message
|
||||
@@ -139,12 +141,11 @@ class transaction(models.mem_bank_transaction):
|
||||
res = []
|
||||
while(len(line) > col * size):
|
||||
separation = (col + 1) * size - 1
|
||||
if line[col * size : separation].strip():
|
||||
part = line[col * size : separation]
|
||||
if line[col * size: separation].strip():
|
||||
part = line[col * size: separation]
|
||||
# If the separation character is not a space, add it anyway
|
||||
# presumably for sepa feedback strings only
|
||||
if (len(line) > separation
|
||||
and line[separation] != ' '):
|
||||
if (len(line) > separation and line[separation] != ' '):
|
||||
part += line[separation]
|
||||
res.append(part)
|
||||
col += 1
|
||||
@@ -180,7 +181,7 @@ class transaction(models.mem_bank_transaction):
|
||||
end_index = start_index + 1
|
||||
while end_index < items_len:
|
||||
key = '/'.join(items[start_index:end_index])
|
||||
if key in known_keys:
|
||||
if key in known_keys:
|
||||
return (key, start_index, end_index)
|
||||
end_index += 1
|
||||
start_index += 1
|
||||
@@ -203,15 +204,16 @@ class transaction(models.mem_bank_transaction):
|
||||
key_info = _get_next_key(items, item_index)
|
||||
value_end_index = (key_info and key_info[1]) or items_len
|
||||
sepa_value = (
|
||||
((value_end_index > item_index)
|
||||
and '/'.join(items[item_index:value_end_index]))
|
||||
(
|
||||
(value_end_index > item_index)
|
||||
and '/'.join(items[item_index:value_end_index]))
|
||||
or '')
|
||||
sepa_dict[sepa_key] = sepa_value
|
||||
return sepa_dict
|
||||
|
||||
def parse_type(field):
|
||||
# here we process the first field, which identifies the statement type
|
||||
# and in case of certain types contains additional information
|
||||
# here we process the first field, which identifies the statement
|
||||
# type and in case of certain types contains additional information
|
||||
transfer_type = 'UNKN'
|
||||
remote_account = False
|
||||
remote_owner = False
|
||||
@@ -233,12 +235,14 @@ class transaction(models.mem_bank_transaction):
|
||||
transfer_type = 'BEA'
|
||||
# columns 6 to 16 contain the terminal identifier
|
||||
# column 17 contains a space
|
||||
# columns 18 to 31 contain date and time in DD.MM.YY/HH.MM format
|
||||
elif field.startswith('GEA '):
|
||||
# columns 18 to 31 contain date and time in DD.MM.YY/HH.MM
|
||||
# format
|
||||
elif field.startswith('GEA '):
|
||||
transfer_type = 'GEA'
|
||||
# columns 6 to 16 contain the terminal identifier
|
||||
# column 17 contains a space
|
||||
# columns 18 to 31 contain date and time in DD.MM.YY/HH.MM format
|
||||
# columns 18 to 31 contain date and time in DD.MM.YY/HH.MM
|
||||
# format
|
||||
elif field.startswith('MAANDBIJDRAGE ABNAMRO'):
|
||||
transfer_type = 'COSTS'
|
||||
elif re.match("^\s([0-9]+\.){3}[0-9]+\s", field):
|
||||
@@ -251,9 +255,10 @@ class transaction(models.mem_bank_transaction):
|
||||
elif field.startswith("TOTAAL BETALINGEN"):
|
||||
transfer_type = 'PAYB'
|
||||
return (transfer_type, remote_account, remote_owner)
|
||||
|
||||
|
||||
fields = split_blob(self.blob)
|
||||
(self.transfer_type, self.remote_account, self.remote_owner) = parse_type(fields[0])
|
||||
(self.transfer_type, self.remote_account, self.remote_owner) = \
|
||||
parse_type(fields[0])
|
||||
|
||||
if self.transfer_type == 'SEPA':
|
||||
sepa_dict = get_sepa_dict(''.join(fields))
|
||||
@@ -263,7 +268,7 @@ class transaction(models.mem_bank_transaction):
|
||||
'SEPA BATCH SALARIS': 'PAYB',
|
||||
'SEPA TERUGBOEKING': 'RETR',
|
||||
}.get(sepa_type.upper(), 'SEPA')
|
||||
self.remote_account = sepa_dict.get('IBAN',False)
|
||||
self.remote_account = sepa_dict.get('IBAN', False)
|
||||
self.remote_bank_bic = sepa_dict.get('BIC', False)
|
||||
self.remote_owner = sepa_dict.get('NAME', False)
|
||||
self.reference = sepa_dict.get('REMI', '')
|
||||
@@ -278,21 +283,24 @@ class transaction(models.mem_bank_transaction):
|
||||
|
||||
elif self.transfer_type == 'BEA':
|
||||
# second column contains remote owner and bank pass identification
|
||||
self.remote_owner = len(fields) > 1 and fields[1].split(',')[0].strip() or False
|
||||
# column 2 and up can contain additional messsages
|
||||
self.remote_owner = (
|
||||
len(fields) > 1 and fields[1].split(',')[0].strip() or False)
|
||||
# column 2 and up can contain additional messsages
|
||||
# (such as transaction costs or currency conversion)
|
||||
self.message = ' '.join(field.strip() for field in fields)
|
||||
|
||||
elif self.transfer_type == 'BANK':
|
||||
# second column contains the remote owner or the first message line
|
||||
if not self.remote_owner:
|
||||
self.remote_owner = len(fields) > 1 and fields[1].strip() or False
|
||||
self.remote_owner = (
|
||||
len(fields) > 1 and fields[1].strip() or False)
|
||||
self.message = ' '.join(field.strip() for field in fields[2:])
|
||||
else:
|
||||
self.message = ' '.join(field.strip() for field in fields[1:])
|
||||
|
||||
elif self.transfer_type == 'INTL':
|
||||
# first column seems to consist of some kind of international transaction id
|
||||
# first column seems to consist of some kind of international
|
||||
# transaction id
|
||||
self.reference = fields[0].strip()
|
||||
# second column seems to contain remote currency and amount
|
||||
# to be processed in a later release of this module
|
||||
@@ -317,12 +325,14 @@ class transaction(models.mem_bank_transaction):
|
||||
# but can be any numeric line really
|
||||
for field in fields[1:]:
|
||||
m = re.match(
|
||||
"^\s*((BETALINGSKENM\.)|(ACCEPTGIRO))?\s*([0-9]+([ /][0-9]+)*)\s*$",
|
||||
"^\s*((BETALINGSKENM\.)|(ACCEPTGIRO))?\s*([0-9]+"
|
||||
"([ /][0-9]+)*)\s*$",
|
||||
field)
|
||||
if m:
|
||||
self.reference = m.group(4)
|
||||
break
|
||||
|
||||
|
||||
class statement(models.mem_bank_statement):
|
||||
'''
|
||||
Implementation of bank_statement communication class of account_banking
|
||||
@@ -335,7 +345,7 @@ class statement(models.mem_bank_statement):
|
||||
self.id = msg.statement_id
|
||||
self.local_account = msg.local_account
|
||||
self.date = str2date(msg.date, '%Y%m%d')
|
||||
self.start_balance = self.end_balance = 0 # msg.start_balance
|
||||
self.start_balance = self.end_balance = 0 # msg.start_balance
|
||||
self.import_transaction(msg)
|
||||
|
||||
def import_transaction(self, msg):
|
||||
@@ -346,6 +356,7 @@ class statement(models.mem_bank_statement):
|
||||
self.end_balance += trans.transferred_amount
|
||||
self.transactions.append(trans)
|
||||
|
||||
|
||||
class parser(models.parser):
|
||||
code = 'ABNAM'
|
||||
country_code = 'NL'
|
||||
@@ -365,7 +376,7 @@ each file covers a period of two weeks.
|
||||
# Transaction lines are not numbered, so keep a tracer
|
||||
subno = 0
|
||||
statement_id = False
|
||||
for line in csv.reader(lines, delimiter = '\t', quoting=csv.QUOTE_NONE):
|
||||
for line in csv.reader(lines, delimiter='\t', quoting=csv.QUOTE_NONE):
|
||||
# Skip empty (last) lines
|
||||
if not line:
|
||||
continue
|
||||
@@ -381,5 +392,3 @@ each file covers a period of two weeks.
|
||||
stmnt = statement(msg)
|
||||
result.append(stmnt)
|
||||
return result
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
# garantees and support are strongly adviced to contract EduSense BV
|
||||
#
|
||||
# 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,
|
||||
@@ -24,7 +24,6 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
import wizard
|
||||
import account_banking_nl_clieop
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
from . import wizard
|
||||
from . import account_banking_nl_clieop
|
||||
|
||||
@@ -4,8 +4,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,
|
||||
|
||||
@@ -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,7 +19,6 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from datetime import date
|
||||
from openerp.osv import orm, fields
|
||||
from openerp.tools.translate import _
|
||||
|
||||
@@ -45,7 +44,7 @@ class clieop_export(orm.Model):
|
||||
'duplicates':
|
||||
fields.integer('Number of Duplicates', readonly=True),
|
||||
'prefered_date':
|
||||
fields.date('Prefered Processing Date',readonly=True),
|
||||
fields.date('Prefered Processing Date', readonly=True),
|
||||
'no_transactions':
|
||||
fields.integer('Number of Transactions', readonly=True),
|
||||
'check_no_accounts':
|
||||
@@ -81,13 +80,13 @@ class clieop_export(orm.Model):
|
||||
'''
|
||||
last = 1
|
||||
last_ids = self.search(cr, uid, [
|
||||
('date_generated', '=',
|
||||
fields.date.context_today(self, cr,uid,context))
|
||||
], context=context)
|
||||
('date_generated', '=', fields.date.context_today(
|
||||
self, cr, uid, context)),
|
||||
], context=context)
|
||||
if last_ids:
|
||||
last = 1 + max([x['daynumber'] for x in self.read(
|
||||
cr, uid, last_ids, ['daynumber'],
|
||||
context=context)])
|
||||
cr, uid, last_ids, ['daynumber'], context=context)]
|
||||
)
|
||||
return last
|
||||
|
||||
_defaults = {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
# Copyright (C) 2011 Therp BV (<http://therp.nl>)
|
||||
#
|
||||
# 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,25 +20,27 @@
|
||||
|
||||
""" This script covers the migration of the payment wizards from old style to
|
||||
new style (osv_memory). It guarantees an easy upgrade for early adopters
|
||||
of the 6.0 branch of this OpenERP module. Note that a migration from OpenERP
|
||||
of the 6.0 branch of this OpenERP module. Note that a migration from OpenERP
|
||||
5.0 to OpenERP 6.0 with respect to this module is not covered by this script.
|
||||
"""
|
||||
|
||||
__name__ = "payment.mode.type:: Add new style payment wizards to existing payment mode types"
|
||||
__name__ = ("payment.mode.type:: Add new style payment wizards to existing "
|
||||
"payment mode types")
|
||||
|
||||
|
||||
def migrate(cr, version):
|
||||
cr.execute ("UPDATE payment_mode_type"
|
||||
" SET ir_model_id = data1.res_id"
|
||||
" FROM ir_model_data data1,"
|
||||
" ir_model_data data2"
|
||||
" WHERE data2.res_id = payment_mode_type.id"
|
||||
" AND data1.module = 'account_banking_nl_clieop'"
|
||||
" AND data1.model = 'ir.model'"
|
||||
" AND data1.name = 'model_banking_export_clieop_wizard'"
|
||||
" AND data2.module = 'account_banking_nl_clieop'"
|
||||
" AND data2.model = 'payment.mode.type'"
|
||||
" AND data2.name IN ('export_clieop_inc',"
|
||||
" 'export_clieop_pay',"
|
||||
" 'export_clieop_sal'"
|
||||
" )"
|
||||
)
|
||||
cr.execute("UPDATE payment_mode_type"
|
||||
" SET ir_model_id = data1.res_id"
|
||||
" FROM ir_model_data data1,"
|
||||
" ir_model_data data2"
|
||||
" WHERE data2.res_id = payment_mode_type.id"
|
||||
" AND data1.module = 'account_banking_nl_clieop'"
|
||||
" AND data1.model = 'ir.model'"
|
||||
" AND data1.name = 'model_banking_export_clieop_wizard'"
|
||||
" AND data2.module = 'account_banking_nl_clieop'"
|
||||
" AND data2.model = 'payment.mode.type'"
|
||||
" AND data2.name IN ('export_clieop_inc',"
|
||||
" 'export_clieop_pay',"
|
||||
" 'export_clieop_sal'"
|
||||
" )"
|
||||
)
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
# Copyright (C) 2011 Therp BV (<http://therp.nl>)
|
||||
#
|
||||
# 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,
|
||||
@@ -18,16 +18,19 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
""" r64: introduction of the payment_mode_type in order to support of debit orders
|
||||
"""r64: introduction of the payment_mode_type in order to support of debit
|
||||
orders
|
||||
"""
|
||||
__name__ = "payment.mode.type:: set payment_mode_type to 'debit' for Clieop incasso export"
|
||||
__name__ = ("payment.mode.type:: set payment_mode_type to 'debit' for Clieop "
|
||||
"incasso export")
|
||||
|
||||
|
||||
def migrate(cr, version):
|
||||
cr.execute ("UPDATE payment_mode_type"
|
||||
" SET payment_order_type = 'debit'"
|
||||
" FROM ir_model_data "
|
||||
" WHERE res_id = payment_mode_type.id"
|
||||
" AND module = 'account_banking_nl_clieop'"
|
||||
" AND model = 'payment.mode.type'"
|
||||
" AND ir_model_data.name = 'export_clieop_inc'"
|
||||
)
|
||||
cr.execute("UPDATE payment_mode_type"
|
||||
" SET payment_order_type = 'debit'"
|
||||
" FROM ir_model_data "
|
||||
" WHERE res_id = payment_mode_type.id"
|
||||
" AND module = 'account_banking_nl_clieop'"
|
||||
" AND model = 'payment.mode.type'"
|
||||
" AND ir_model_data.name = 'export_clieop_inc'"
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
@@ -18,6 +18,5 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
import export_clieop
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
from . import export_clieop
|
||||
|
||||
@@ -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,
|
||||
@@ -26,7 +26,8 @@ __all__ = ['DirectDebitBatch', 'PaymentsBatch', 'DirectDebit', 'Payment',
|
||||
'DirectDebitFile', 'PaymentsFile', 'SalaryPaymentsFile',
|
||||
'SalaryPaymentOrder', 'PaymentOrder', 'DirectDebitOrder',
|
||||
'OrdersFile',
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
class SWIFTField(record.Field):
|
||||
'''
|
||||
@@ -37,27 +38,24 @@ class SWIFTField(record.Field):
|
||||
kwargs['cast'] = convert.to_swift
|
||||
super(SWIFTField, self).__init__(*args, **kwargs)
|
||||
|
||||
#def take(self, buffer):
|
||||
# return convert.to_swift(super(SWIFTField, self).take(buffer))
|
||||
|
||||
#def format(self, value):
|
||||
# return convert.to_swift(super(SWIFTField, self).format(value))
|
||||
|
||||
class SWIFTFieldNoLeadingWhitespace(SWIFTField):
|
||||
def format(self, value):
|
||||
return super(SWIFTFieldNoLeadingWhitespace, self).format(
|
||||
self.cast(value).lstrip())
|
||||
|
||||
|
||||
def eleven_test(s):
|
||||
'''
|
||||
Dutch eleven-test for validating 9-long local bank account numbers.
|
||||
'''
|
||||
r = 0
|
||||
l = len(s)
|
||||
for i,c in enumerate(s):
|
||||
for i, c in enumerate(s):
|
||||
r += (l-i) * int(c)
|
||||
return (r % 11) == 0
|
||||
|
||||
|
||||
def chunk(str, length):
|
||||
'''
|
||||
Split a string in equal sized substrings of length <length>
|
||||
@@ -66,7 +64,8 @@ def chunk(str, length):
|
||||
yield str[:length]
|
||||
str = str[length:]
|
||||
|
||||
class HeaderRecord(record.Record): #{{{
|
||||
|
||||
class HeaderRecord(record.Record):
|
||||
'''ClieOp3 header record'''
|
||||
_fields = [
|
||||
record.Filler('recordcode', 4, '0001'),
|
||||
@@ -84,7 +83,7 @@ class HeaderRecord(record.Record): #{{{
|
||||
self.sender_id = id or ''
|
||||
self.file_id = '%02d%02d' % (self.creation_date.day, seqno)
|
||||
self.duplicatecode = duplicate and '2' or '1'
|
||||
#}}}
|
||||
|
||||
|
||||
class FooterRecord(record.Record):
|
||||
'''ClieOp3 footer record'''
|
||||
@@ -94,6 +93,7 @@ class FooterRecord(record.Record):
|
||||
record.Filler('filler', 45),
|
||||
]
|
||||
|
||||
|
||||
class BatchHeaderRecord(record.Record):
|
||||
'''Header record preceding new batches'''
|
||||
_fields = [
|
||||
@@ -107,6 +107,7 @@ class BatchHeaderRecord(record.Record):
|
||||
record.Filler('filler', 10),
|
||||
]
|
||||
|
||||
|
||||
class BatchFooterRecord(record.Record):
|
||||
'''Closing record for batches'''
|
||||
_fields = [
|
||||
@@ -118,6 +119,7 @@ class BatchFooterRecord(record.Record):
|
||||
record.Filler('filler', 10),
|
||||
]
|
||||
|
||||
|
||||
class FixedMessageRecord(record.Record):
|
||||
'''Fixed message'''
|
||||
_fields = [
|
||||
@@ -127,6 +129,7 @@ class FixedMessageRecord(record.Record):
|
||||
record.Filler('filler', 13),
|
||||
]
|
||||
|
||||
|
||||
class SenderRecord(record.Record):
|
||||
'''Ordering party'''
|
||||
_fields = [
|
||||
@@ -140,6 +143,7 @@ class SenderRecord(record.Record):
|
||||
record.Filler('filler', 2),
|
||||
]
|
||||
|
||||
|
||||
class TransactionRecord(record.Record):
|
||||
'''Transaction'''
|
||||
_fields = [
|
||||
@@ -152,6 +156,7 @@ class TransactionRecord(record.Record):
|
||||
record.Filler('filler', 9),
|
||||
]
|
||||
|
||||
|
||||
class NamePayerRecord(record.Record):
|
||||
'''Name payer'''
|
||||
_fields = [
|
||||
@@ -161,6 +166,7 @@ class NamePayerRecord(record.Record):
|
||||
record.Filler('filler', 10),
|
||||
]
|
||||
|
||||
|
||||
class PaymentReferenceRecord(record.Record):
|
||||
'''Payment reference'''
|
||||
_fields = [
|
||||
@@ -170,6 +176,7 @@ class PaymentReferenceRecord(record.Record):
|
||||
record.Filler('filler', 29),
|
||||
]
|
||||
|
||||
|
||||
class DescriptionRecord(record.Record):
|
||||
'''Description'''
|
||||
_fields = [
|
||||
@@ -179,6 +186,7 @@ class DescriptionRecord(record.Record):
|
||||
record.Filler('filler', 13),
|
||||
]
|
||||
|
||||
|
||||
class NameBeneficiaryRecord(record.Record):
|
||||
'''Name receiving party'''
|
||||
_fields = [
|
||||
@@ -188,6 +196,7 @@ class NameBeneficiaryRecord(record.Record):
|
||||
record.Filler('filler', 10),
|
||||
]
|
||||
|
||||
|
||||
class OrderRecord(record.Record):
|
||||
'''Order details'''
|
||||
_fields = [
|
||||
@@ -203,23 +212,28 @@ class OrderRecord(record.Record):
|
||||
record.Filler('currency', 3, 'EUR'),
|
||||
record.Field('testcode', 1),
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(OrderRecord, self).__init__(*args, **kwargs)
|
||||
self.batch_medium = 'DATACOM'
|
||||
self.name_transactioncode = self._transactioncode
|
||||
|
||||
|
||||
class SalaryPaymentOrder(OrderRecord):
|
||||
'''Salary payment batch record'''
|
||||
_transactioncode = 'SALARIS'
|
||||
|
||||
|
||||
class PaymentOrder(OrderRecord):
|
||||
'''Payment batch record'''
|
||||
_transactioncode = 'CREDBET'
|
||||
|
||||
|
||||
class DirectDebitOrder(OrderRecord):
|
||||
'''Direct debit payments batch record'''
|
||||
_transactioncode = 'INCASSO'
|
||||
|
||||
|
||||
class Optional(object):
|
||||
'''Auxilliary class to handle optional records'''
|
||||
def __init__(self, klass, max=1):
|
||||
@@ -233,7 +247,7 @@ class Optional(object):
|
||||
super(Optional, self).__setattr__(attr, value)
|
||||
else:
|
||||
if self._guts and len(self._guts) > self._max:
|
||||
raise ValueError, 'Only %d lines are allowed' % self._max
|
||||
raise ValueError('Only %d lines are allowed' % self._max)
|
||||
newitem = self._klass()
|
||||
setattr(newitem, attr, value)
|
||||
self._guts.append(newitem)
|
||||
@@ -259,6 +273,7 @@ class Optional(object):
|
||||
'''Make sure to adapt'''
|
||||
return self._guts.__iter__()
|
||||
|
||||
|
||||
class OrdersFile(object):
|
||||
'''A payment orders file'''
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -271,18 +286,19 @@ class OrdersFile(object):
|
||||
'''
|
||||
return '\r\n'.join(self.orders)
|
||||
|
||||
|
||||
class Transaction(object):
|
||||
'''Generic transaction class'''
|
||||
def __init__(self, type_=0, name=None, reference=None, messages=[],
|
||||
accountno_beneficiary=None, accountno_payer=None,
|
||||
amount=0
|
||||
):
|
||||
amount=0):
|
||||
self.transaction = TransactionRecord()
|
||||
self.paymentreference = Optional(PaymentReferenceRecord)
|
||||
self.description = Optional(DescriptionRecord, 4)
|
||||
self.transaction.transactiontype = type_
|
||||
# Remove Postbank account marker 'P'
|
||||
self.transaction.accountno_beneficiary = accountno_beneficiary.replace('P', '0')
|
||||
self.transaction.accountno_beneficiary = accountno_beneficiary.replace(
|
||||
'P', '0')
|
||||
self.transaction.accountno_payer = accountno_payer.replace('P', '0')
|
||||
self.transaction.amount = int(round(amount * 100))
|
||||
if reference:
|
||||
@@ -290,8 +306,7 @@ class Transaction(object):
|
||||
# Allow long message lines to redistribute over multiple message
|
||||
# records
|
||||
for msg in chunk(''.join(messages),
|
||||
self.description.length('description')
|
||||
):
|
||||
self.description.length('description')):
|
||||
try:
|
||||
self.description.description = msg
|
||||
except ValueError:
|
||||
@@ -321,13 +336,14 @@ class DirectDebit(Transaction):
|
||||
items.append(str(description))
|
||||
return '\r\n'.join(items)
|
||||
|
||||
|
||||
class Payment(Transaction):
|
||||
'''Payment transaction'''
|
||||
def __init__(self, *args, **kwargs):
|
||||
reknr = kwargs['accountno_beneficiary']
|
||||
if len(reknr.lstrip('0')) > 7:
|
||||
if not eleven_test(reknr):
|
||||
raise ValueError, '%s is not a valid bank account' % reknr
|
||||
raise ValueError('%s is not a valid bank account' % reknr)
|
||||
kwargs['type_'] = 5
|
||||
self.name = NameBeneficiaryRecord()
|
||||
super(Payment, self).__init__(*args, **kwargs)
|
||||
@@ -346,6 +362,7 @@ class Payment(Transaction):
|
||||
items.append(str(self.name))
|
||||
return '\r\n'.join(items)
|
||||
|
||||
|
||||
class SalaryPayment(Payment):
|
||||
'''Salary Payment transaction'''
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -353,14 +370,14 @@ class SalaryPayment(Payment):
|
||||
kwargs['type_'] = len(reknr.lstrip('0')) <= 7 and 3 or 8
|
||||
super(SalaryPayment, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class Batch(object):
|
||||
'''Generic batch class'''
|
||||
transactionclass = None
|
||||
|
||||
def __init__(self, sender, rekeningnr, execution_date=None,
|
||||
test=True, messages=[], transactiongroup=None,
|
||||
batch_tracer=1, batch_id=''
|
||||
):
|
||||
batch_tracer=1, batch_id=''):
|
||||
self.header = BatchHeaderRecord()
|
||||
self.fixed_message = Optional(FixedMessageRecord, 4)
|
||||
self.sender = SenderRecord()
|
||||
@@ -386,18 +403,16 @@ class Batch(object):
|
||||
@property
|
||||
def total_amount(self):
|
||||
'''total amount transferred'''
|
||||
return reduce(lambda x,y: x + int(y.transaction.amount),
|
||||
self.transactions, 0
|
||||
)
|
||||
return reduce(lambda x, y: x + int(y.transaction.amount),
|
||||
self.transactions, 0)
|
||||
|
||||
@property
|
||||
def total_accountnos(self):
|
||||
'''check number on account numbers'''
|
||||
return reduce(lambda x,y:
|
||||
x + int(y.transaction.accountno_payer) + \
|
||||
int(y.transaction.accountno_beneficiary),
|
||||
self.transactions, 0
|
||||
)
|
||||
return reduce(lambda x, y:
|
||||
x + int(y.transaction.accountno_payer) +
|
||||
int(y.transaction.accountno_beneficiary),
|
||||
self.transactions, 0)
|
||||
|
||||
@property
|
||||
def rawdata(self):
|
||||
@@ -423,18 +438,22 @@ class Batch(object):
|
||||
self.transactions.append(retval)
|
||||
return retval
|
||||
|
||||
|
||||
class DirectDebitBatch(Batch):
|
||||
'''Direct Debig Payment batch'''
|
||||
transactionclass = DirectDebit
|
||||
|
||||
|
||||
class PaymentsBatch(Batch):
|
||||
'''Payment batch'''
|
||||
transactionclass = Payment
|
||||
|
||||
|
||||
class SalaryBatch(Batch):
|
||||
'''Salary payment class'''
|
||||
transactionclass = SalaryPayment
|
||||
|
||||
|
||||
class ClieOpFile(object):
|
||||
'''The grand unifying class'''
|
||||
def __init__(self, identification='1', execution_date=None,
|
||||
@@ -461,7 +480,7 @@ class ClieOpFile(object):
|
||||
def batch(self, *args, **kwargs):
|
||||
'''Create batch'''
|
||||
kwargs['transactiongroup'] = self.transactiongroup
|
||||
kwargs['batch_tracer'] = len(self.batches) +1
|
||||
kwargs['batch_tracer'] = len(self.batches) + 1
|
||||
kwargs['execution_date'] = self._execution_date
|
||||
kwargs['test'] = self._test
|
||||
args = (self._name_sender, self._accno_sender)
|
||||
@@ -489,22 +508,22 @@ class ClieOpFile(object):
|
||||
retval.total_accountnos = total_accountnos
|
||||
return retval
|
||||
|
||||
|
||||
class DirectDebitFile(ClieOpFile):
|
||||
'''Direct Debit Payments file'''
|
||||
transactiongroup = '10'
|
||||
batchclass = DirectDebitBatch
|
||||
orderclass = DirectDebitOrder
|
||||
|
||||
|
||||
class PaymentsFile(ClieOpFile):
|
||||
'''Payments file'''
|
||||
transactiongroup = '00'
|
||||
batchclass = PaymentsBatch
|
||||
orderclass = PaymentOrder
|
||||
|
||||
|
||||
class SalaryPaymentsFile(PaymentsFile):
|
||||
'''Salary Payments file'''
|
||||
batchclass = SalaryBatch
|
||||
orderclass = SalaryPaymentOrder
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -21,7 +21,7 @@
|
||||
##############################################################################
|
||||
|
||||
import base64
|
||||
from datetime import datetime, date, timedelta
|
||||
from datetime import datetime, timedelta
|
||||
from openerp.osv import orm, fields
|
||||
from openerp.tools.translate import _
|
||||
from openerp import netsvc
|
||||
@@ -29,14 +29,17 @@ from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
|
||||
from openerp.addons.account_banking import sepa
|
||||
from openerp.addons.account_banking_nl_clieop.wizard import clieop
|
||||
|
||||
|
||||
def strpdate(arg):
|
||||
'''shortcut'''
|
||||
return datetime.strptime(arg, DEFAULT_SERVER_DATE_FORMAT).date()
|
||||
|
||||
|
||||
def strfdate(arg):
|
||||
'''shortcut'''
|
||||
return arg.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
|
||||
|
||||
class banking_export_clieop_wizard(orm.TransientModel):
|
||||
_name = 'banking.export.clieop.wizard'
|
||||
_description = 'Client Opdrachten Export'
|
||||
@@ -67,10 +70,9 @@ class banking_export_clieop_wizard(orm.TransientModel):
|
||||
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.'),
|
||||
),
|
||||
'test': fields.boolean(
|
||||
'Test Run',
|
||||
@@ -80,9 +82,8 @@ class banking_export_clieop_wizard(orm.TransientModel):
|
||||
),
|
||||
'fixed_message': fields.char(
|
||||
'Fixed Message', size=32,
|
||||
help=('A fixed message to apply to all transactions in addition to '
|
||||
'the individual messages.'
|
||||
),
|
||||
help=('A fixed message to apply to all transactions in addition '
|
||||
'to the individual messages.'),
|
||||
),
|
||||
# file fields
|
||||
'file_id': fields.many2one(
|
||||
@@ -90,7 +91,7 @@ class banking_export_clieop_wizard(orm.TransientModel):
|
||||
'ClieOp File',
|
||||
readonly=True
|
||||
),
|
||||
# fields.related does not seem to support
|
||||
# fields.related does not seem to support
|
||||
# fields of type selection
|
||||
'testcode': fields.selection(
|
||||
[('T', _('Yes')), ('P', _('No'))],
|
||||
@@ -175,7 +176,9 @@ class banking_export_clieop_wizard(orm.TransientModel):
|
||||
Also mind that rates for batches are way higher than those for
|
||||
transactions. It pays to limit the number of batches.
|
||||
'''
|
||||
today = strpdate(fields.date.context_today(self, cr, uid, context=context))
|
||||
today = strpdate(fields.date.context_today(
|
||||
self, cr, uid, context=context
|
||||
))
|
||||
payment_order_obj = self.pool.get('payment.order')
|
||||
|
||||
# Payment order ids are provided in the context
|
||||
@@ -199,12 +202,14 @@ class banking_export_clieop_wizard(orm.TransientModel):
|
||||
elif payment_order.date_prefered == 'now':
|
||||
execution_date = today
|
||||
elif payment_order.date_prefered == 'due':
|
||||
# Max processing date is 30 days past now, so limiting beyond that
|
||||
# will catch too early payments
|
||||
# Max processing date is 30 days past now, so limiting beyond
|
||||
# that will catch too early payments
|
||||
max_date = execution_date = today + timedelta(days=31)
|
||||
for line in payment_order.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
|
||||
else:
|
||||
@@ -212,7 +217,8 @@ class banking_export_clieop_wizard(orm.TransientModel):
|
||||
if execution_date and execution_date >= max_date:
|
||||
raise orm.except_orm(
|
||||
_('Error'),
|
||||
_('You can\'t create ClieOp orders more than 30 days in advance.')
|
||||
_('You can\'t create ClieOp orders more than 30 days '
|
||||
'in advance.')
|
||||
)
|
||||
if len(runs) != 1:
|
||||
raise orm.except_orm(
|
||||
@@ -233,17 +239,19 @@ class banking_export_clieop_wizard(orm.TransientModel):
|
||||
'''
|
||||
Wizard to actually create the ClieOp3 file
|
||||
'''
|
||||
payment_order_obj = self.pool.get('payment.order')
|
||||
clieop_export = self.browse(cr, uid, ids, context)[0]
|
||||
clieopfile = None
|
||||
for payment_order in clieop_export.payment_order_ids:
|
||||
if not clieopfile:
|
||||
# Just once: create clieop file
|
||||
our_account_owner = payment_order.mode.bank_id.owner_name \
|
||||
or payment_order.mode.bank_id.partner_id.name
|
||||
our_account_owner = (
|
||||
payment_order.mode.bank_id.owner_name
|
||||
or payment_order.mode.bank_id.partner_id.name
|
||||
)
|
||||
|
||||
if payment_order.mode.bank_id.state == 'iban':
|
||||
our_account_nr = payment_order.mode.bank_id.acc_number_domestic
|
||||
our_account_nr = (
|
||||
payment_order.mode.bank_id.acc_number_domestic)
|
||||
if not our_account_nr:
|
||||
our_account_nr = sepa.IBAN(
|
||||
payment_order.mode.bank_id.acc_number
|
||||
@@ -253,21 +261,22 @@ class banking_export_clieop_wizard(orm.TransientModel):
|
||||
if not our_account_nr:
|
||||
raise orm.except_orm(
|
||||
_('Error'),
|
||||
_('Your bank account has to have a valid account number')
|
||||
_('Your bank account has to have a valid account '
|
||||
'number')
|
||||
)
|
||||
clieopfile = {'CLIEOPPAY': clieop.PaymentsFile,
|
||||
'CLIEOPINC': clieop.DirectDebitFile,
|
||||
'CLIEOPSAL': clieop.SalaryPaymentsFile,
|
||||
}[clieop_export['batchtype']](
|
||||
identification = clieop_export['reference'],
|
||||
execution_date = clieop_export['execution_date'],
|
||||
name_sender = our_account_owner,
|
||||
accountno_sender = our_account_nr,
|
||||
seqno = self.pool.get(
|
||||
'banking.export.clieop').get_daynr(
|
||||
cr, uid, context=context),
|
||||
test = clieop_export['test']
|
||||
)
|
||||
clieopfile = {
|
||||
'CLIEOPPAY': clieop.PaymentsFile,
|
||||
'CLIEOPINC': clieop.DirectDebitFile,
|
||||
'CLIEOPSAL': clieop.SalaryPaymentsFile,
|
||||
}[clieop_export['batchtype']](
|
||||
identification=clieop_export['reference'],
|
||||
execution_date=clieop_export['execution_date'],
|
||||
name_sender=our_account_owner,
|
||||
accountno_sender=our_account_nr,
|
||||
seqno=self.pool.get('banking.export.clieop').get_daynr(
|
||||
cr, uid, context=context),
|
||||
test=clieop_export['test']
|
||||
)
|
||||
|
||||
# ClieOp3 files can contain multiple batches, but we put all
|
||||
# orders into one single batch. Ratio behind this is that a
|
||||
@@ -282,8 +291,8 @@ class banking_export_clieop_wizard(orm.TransientModel):
|
||||
# The first payment order processed sets the reference of the
|
||||
# batch.
|
||||
batch = clieopfile.batch(
|
||||
messages = messages,
|
||||
batch_id = clieop_export['reference']
|
||||
messages=messages,
|
||||
batch_id=clieop_export['reference']
|
||||
)
|
||||
|
||||
for line in payment_order.line_ids:
|
||||
@@ -294,12 +303,13 @@ class banking_export_clieop_wizard(orm.TransientModel):
|
||||
_('There is insufficient information.\r\n'
|
||||
'Both destination address and account '
|
||||
'number must be provided'
|
||||
)
|
||||
)
|
||||
)
|
||||
kwargs = dict(
|
||||
name = line.bank_id.owner_name or line.bank_id.partner_id.name,
|
||||
amount = line.amount_currency,
|
||||
reference = line.communication or None,
|
||||
name=line.bank_id.owner_name
|
||||
or line.bank_id.partner_id.name,
|
||||
amount=line.amount_currency,
|
||||
reference=line.communication or None,
|
||||
)
|
||||
if line.communication2:
|
||||
kwargs['messages'] = [line.communication2]
|
||||
@@ -324,32 +334,32 @@ class banking_export_clieop_wizard(orm.TransientModel):
|
||||
else:
|
||||
kwargs['accountno_beneficiary'] = other_account_nr
|
||||
kwargs['accountno_payer'] = our_account_nr
|
||||
transaction = batch.transaction(**kwargs)
|
||||
batch.transaction(**kwargs)
|
||||
|
||||
# Generate the specifics of this clieopfile
|
||||
order = clieopfile.order
|
||||
file_id = self.pool.get('banking.export.clieop').create(
|
||||
cr, uid, dict(
|
||||
filetype = order.name_transactioncode,
|
||||
identification = order.identification,
|
||||
prefered_date = strfdate(order.preferred_execution_date),
|
||||
total_amount = int(order.total_amount) / 100.0,
|
||||
check_no_accounts = order.total_accountnos,
|
||||
no_transactions = order.nr_posts,
|
||||
testcode = order.testcode,
|
||||
file = base64.encodestring(clieopfile.rawdata),
|
||||
filename = 'Clieop03-{0}.txt'.format(order.identification),
|
||||
daynumber = int(clieopfile.header.file_id[2:]),
|
||||
payment_order_ids = [
|
||||
[6, 0, [x.id for x in clieop_export['payment_order_ids']]]
|
||||
],
|
||||
), context)
|
||||
self.write(cr, uid, [ids[0]], dict(
|
||||
filetype = order.name_transactioncode,
|
||||
testcode = order.testcode,
|
||||
file_id = file_id,
|
||||
state = 'finish',
|
||||
filetype=order.name_transactioncode,
|
||||
identification=order.identification,
|
||||
prefered_date=strfdate(order.preferred_execution_date),
|
||||
total_amount=int(order.total_amount) / 100.0,
|
||||
check_no_accounts=order.total_accountnos,
|
||||
no_transactions=order.nr_posts,
|
||||
testcode=order.testcode,
|
||||
file=base64.encodestring(clieopfile.rawdata),
|
||||
filename='Clieop03-{0}.txt'.format(order.identification),
|
||||
daynumber=int(clieopfile.header.file_id[2:]),
|
||||
payment_order_ids=[
|
||||
[6, 0, [x.id
|
||||
for x in clieop_export['payment_order_ids']]]],
|
||||
), context)
|
||||
self.write(cr, uid, [ids[0]], dict(
|
||||
filetype=order.name_transactioncode,
|
||||
testcode=order.testcode,
|
||||
file_id=file_id,
|
||||
state='finish',
|
||||
), context)
|
||||
return {
|
||||
'name': _('Client Opdrachten Export'),
|
||||
'view_type': 'form',
|
||||
@@ -367,7 +377,9 @@ class banking_export_clieop_wizard(orm.TransientModel):
|
||||
Cancel the ClieOp: just drop the file
|
||||
'''
|
||||
clieop_export = self.read(cr, uid, ids, ['file_id'], context)[0]
|
||||
self.pool.get('banking.export.clieop').unlink(cr, uid, clieop_export['file_id'][0])
|
||||
self.pool.get('banking.export.clieop').unlink(
|
||||
cr, uid, clieop_export['file_id'][0]
|
||||
)
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
def save_clieop(self, cr, uid, ids, context):
|
||||
@@ -378,11 +390,12 @@ class banking_export_clieop_wizard(orm.TransientModel):
|
||||
cr, uid, ids, context)[0]
|
||||
if not clieop_export['test']:
|
||||
clieop_obj = self.pool.get('banking.export.clieop')
|
||||
payment_order_obj = self.pool.get('payment.order')
|
||||
clieop_file = clieop_obj.write(
|
||||
clieop_obj.write(
|
||||
cr, uid, clieop_export['file_id'].id, {'state': 'sent'}
|
||||
)
|
||||
wf_service = netsvc.LocalService('workflow')
|
||||
for order in clieop_export['payment_order_ids']:
|
||||
wf_service.trg_validate(uid, 'payment.order', order.id, 'sent', cr)
|
||||
wf_service.trg_validate(
|
||||
uid, 'payment.order', order.id, 'sent', cr
|
||||
)
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
# garantees and support are strongly adviced to contract EduSense BV
|
||||
#
|
||||
# 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,
|
||||
@@ -24,6 +24,5 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
import girotel
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
from . import girotel
|
||||
|
||||
@@ -4,8 +4,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,
|
||||
@@ -26,7 +26,6 @@
|
||||
'category': 'Account Banking',
|
||||
'depends': ['account_banking'],
|
||||
'data': [
|
||||
#'security/ir.model.access.csv',
|
||||
],
|
||||
'description': '''
|
||||
Module to import Dutch Girotel format transation files.
|
||||
|
||||
@@ -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,
|
||||
@@ -40,7 +40,8 @@ Assumptions:
|
||||
4. the data comes from the SWIFT-network (limited ASCII)
|
||||
|
||||
Assumption 4 seems not always true, leading to wrong character conversions.
|
||||
As a counter measure, all imported data is converted to SWIFT-format before usage.
|
||||
As a counter measure, all imported data is converted to SWIFT-format before
|
||||
usage.
|
||||
'''
|
||||
from account_banking.parsers import models
|
||||
from account_banking.parsers.convert import str2date, to_swift
|
||||
@@ -52,6 +53,7 @@ bt = models.mem_bank_transaction
|
||||
|
||||
__all__ = ['parser']
|
||||
|
||||
|
||||
class transaction_message(object):
|
||||
'''
|
||||
A auxiliary class to validate and coerce read values
|
||||
@@ -81,7 +83,12 @@ class transaction_message(object):
|
||||
Convert values from string content to SWIFT-allowable content
|
||||
'''
|
||||
retval = super(transaction_message, self).__getattribute__(attr)
|
||||
return attr != 'strattrs' and attr in self.strattrs and to_swift(retval) or retval
|
||||
return attr != (
|
||||
'strattrs'
|
||||
and attr in self.strattrs
|
||||
and to_swift(retval)
|
||||
or retval
|
||||
)
|
||||
|
||||
def genid(self):
|
||||
'''
|
||||
@@ -99,14 +106,15 @@ class transaction_message(object):
|
||||
Initialize own dict with attributes and coerce values to right type
|
||||
'''
|
||||
if len(self.attrnames) != len(values):
|
||||
raise ValueError, \
|
||||
_('Invalid transaction line: expected %d columns, found %d') \
|
||||
% (len(self.attrnames), len(values))
|
||||
raise ValueError(
|
||||
_('Invalid transaction line: expected %d columns, found %d')
|
||||
% (len(self.attrnames), len(values))
|
||||
)
|
||||
self.__dict__.update(dict(zip(self.attrnames, values)))
|
||||
self.date = str2date(self.date, '%Y%m%d')
|
||||
if self.direction == 'A':
|
||||
self.transferred_amount = -float(self.transferred_amount)
|
||||
#payment batch done via clieop
|
||||
# payment batch done via clieop
|
||||
if (self.transfer_type == 'VZ'
|
||||
and (not self.remote_account or self.remote_account == '0')
|
||||
and (not self.message or re.match('^\s*$', self.message))
|
||||
@@ -114,7 +122,7 @@ class transaction_message(object):
|
||||
self.transfer_type = 'PB'
|
||||
self.message = self.remote_owner
|
||||
self.remove_owner = False
|
||||
#payment batch done via sepa
|
||||
# payment batch done via sepa
|
||||
if self.transfer_type == 'VZ'\
|
||||
and not self.remote_account\
|
||||
and not self.remote_owner\
|
||||
@@ -144,10 +152,10 @@ class transaction(models.mem_bank_transaction):
|
||||
'''
|
||||
Implementation of transaction communication class for account_banking.
|
||||
'''
|
||||
attrnames = [ 'statement_id', 'remote_account', 'remote_owner',
|
||||
attrnames = ['statement_id', 'remote_account', 'remote_owner',
|
||||
'remote_currency', 'transferred_amount', 'execution_date',
|
||||
'value_date', 'transfer_type', 'message',
|
||||
]
|
||||
]
|
||||
|
||||
type_map = {
|
||||
'BA': bt.PAYMENT_TERMINAL,
|
||||
@@ -225,7 +233,7 @@ class transaction(models.mem_bank_transaction):
|
||||
self.reference = self.remote_owner.rstrip()
|
||||
parts = [self.message[i:i+32].rstrip()
|
||||
for i in range(0, len(self.message), 32)
|
||||
]
|
||||
]
|
||||
if len(parts) > 3:
|
||||
self.reference = parts[-1]
|
||||
self.message = '\n'.join(parts[:-1])
|
||||
@@ -246,7 +254,7 @@ class transaction(models.mem_bank_transaction):
|
||||
self.message = self.refold_message(self.message)
|
||||
self.reference = '%s %s' % (self.remote_owner,
|
||||
' '.join(self.message.split()[2:4])
|
||||
)
|
||||
)
|
||||
|
||||
elif self.transfer_type == 'IC':
|
||||
# Direct debit - remote_owner containts reference, while
|
||||
@@ -256,7 +264,7 @@ class transaction(models.mem_bank_transaction):
|
||||
# taxes, but then a once-only manual correction is sufficient.
|
||||
parts = [self.message[i:i+32].rstrip()
|
||||
for i in range(0, len(self.message), 32)
|
||||
]
|
||||
]
|
||||
self.reference = self.remote_owner
|
||||
|
||||
if not parts:
|
||||
@@ -306,6 +314,7 @@ class transaction(models.mem_bank_transaction):
|
||||
# message parts.
|
||||
self.message = self.refold_message(self.message)
|
||||
|
||||
|
||||
class statement(models.mem_bank_statement):
|
||||
'''
|
||||
Implementation of bank_statement communication class of account_banking
|
||||
@@ -329,6 +338,7 @@ class statement(models.mem_bank_statement):
|
||||
self.end_balance += trans.transferred_amount
|
||||
self.transactions.append(trans)
|
||||
|
||||
|
||||
class parser(models.parser):
|
||||
code = 'NLGT'
|
||||
name = _('Dutch Girotel - Kommagescheiden')
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
# Copyright (C) 2011 Therp BV (<http://therp.nl>)
|
||||
# (C) 2011 Smile BV (<http://smile.fr>)
|
||||
#
|
||||
# Based on account-banking
|
||||
# Based on account-banking
|
||||
# (C) 2009 - 2011 EduSense BV (<http://www.edusense.nl>)
|
||||
#
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
|
||||
@@ -24,10 +24,8 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from datetime import datetime
|
||||
from openerp.addons.account_banking.parsers import models
|
||||
from openerp.addons.account_banking.parsers.convert import str2date
|
||||
from openerp.addons.account_banking.sepa import postalcode
|
||||
from openerp.tools.translate import _
|
||||
|
||||
import re
|
||||
@@ -39,10 +37,12 @@ bt = models.mem_bank_transaction
|
||||
|
||||
"""
|
||||
First line states the legend
|
||||
"Datum","Naam / Omschrijving","Rekening","Tegenrekening","Code","Af Bij","Bedrag (EUR)","MutatieSoort","Mededelingen
|
||||
"Datum","Naam / Omschrijving","Rekening","Tegenrekening","Code","Af Bij",\
|
||||
"Bedrag (EUR)","MutatieSoort","Mededelingen
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class transaction_message(object):
|
||||
'''
|
||||
A auxiliary class to validate and coerce read values
|
||||
@@ -68,16 +68,18 @@ class transaction_message(object):
|
||||
if self.debcred == 'Af':
|
||||
self.transferred_amount = -self.transferred_amount
|
||||
try:
|
||||
self.execution_date = self.value_date = str2date(self.date, '%Y%m%d')
|
||||
self.execution_date = self.value_date = str2date(self.date,
|
||||
'%Y%m%d')
|
||||
except ValueError:
|
||||
self.execution_date = self.value_date = str2date(self.date, '%d-%m-%Y')
|
||||
self.statement_id = '' #self.value_date.strftime('%Yw%W')
|
||||
self.execution_date = self.value_date = str2date(self.date,
|
||||
'%d-%m-%Y')
|
||||
self.statement_id = '' # self.value_date.strftime('%Yw%W')
|
||||
self.id = str(subno).zfill(4)
|
||||
self.reference = ''
|
||||
# Normalize basic account numbers
|
||||
self.remote_account = self.remote_account.replace('.', '').zfill(10)
|
||||
self.local_account = self.local_account.replace('.', '').zfill(10)
|
||||
|
||||
self.local_account = self.local_account.replace('.', '').zfill(10)
|
||||
|
||||
|
||||
class transaction(models.mem_bank_transaction):
|
||||
'''
|
||||
@@ -87,30 +89,30 @@ class transaction(models.mem_bank_transaction):
|
||||
'remote_owner', 'transferred_amount',
|
||||
'execution_date', 'value_date', 'transfer_type',
|
||||
'id', 'reference', 'statement_id', 'message',
|
||||
]
|
||||
]
|
||||
|
||||
"""
|
||||
Presumably the same transaction types occur in the MT940 format of ING.
|
||||
From www.ing.nl/Images/MT940_Technische_handleiding_tcm7-69020.pdf
|
||||
|
||||
|
||||
"""
|
||||
type_map = {
|
||||
|
||||
'AC': bt.ORDER, # Acceptgiro
|
||||
'BA': bt.PAYMENT_TERMINAL, # Betaalautomaattransactie
|
||||
'CH': bt.ORDER, # Cheque
|
||||
'DV': bt.ORDER, # Diversen
|
||||
'FL': bt.BANK_TERMINAL, # Filiaalboeking, concernboeking
|
||||
'GF': bt.ORDER, # Telefonisch bankieren
|
||||
'GM': bt.BANK_TERMINAL, # Geldautomaat
|
||||
'GT': bt.ORDER, # Internetbankieren
|
||||
'IC': bt.DIRECT_DEBIT, # Incasso
|
||||
'OV': bt.ORDER, # Overschrijving
|
||||
'PK': bt.BANK_TERMINAL, # Opname kantoor
|
||||
'PO': bt.ORDER, # Periodieke overschrijving
|
||||
'ST': bt.BANK_TERMINAL, # Storting (eigen rekening of derde)
|
||||
'VZ': bt.ORDER, # Verzamelbetaling
|
||||
'NO': bt.STORNO, # Storno
|
||||
|
||||
'AC': bt.ORDER, # Acceptgiro
|
||||
'BA': bt.PAYMENT_TERMINAL, # Betaalautomaattransactie
|
||||
'CH': bt.ORDER, # Cheque
|
||||
'DV': bt.ORDER, # Diversen
|
||||
'FL': bt.BANK_TERMINAL, # Filiaalboeking, concernboeking
|
||||
'GF': bt.ORDER, # Telefonisch bankieren
|
||||
'GM': bt.BANK_TERMINAL, # Geldautomaat
|
||||
'GT': bt.ORDER, # Internetbankieren
|
||||
'IC': bt.DIRECT_DEBIT, # Incasso
|
||||
'OV': bt.ORDER, # Overschrijving
|
||||
'PK': bt.BANK_TERMINAL, # Opname kantoor
|
||||
'PO': bt.ORDER, # Periodieke overschrijving
|
||||
'ST': bt.BANK_TERMINAL, # Storting (eigen rekening of derde)
|
||||
'VZ': bt.ORDER, # Verzamelbetaling
|
||||
'NO': bt.STORNO, # Storno
|
||||
}
|
||||
|
||||
# global expression for matching storno references
|
||||
@@ -167,10 +169,6 @@ class transaction(models.mem_bank_transaction):
|
||||
in the 'name' column, as well as full address information
|
||||
in the 'message' column'
|
||||
"""
|
||||
reference = ''
|
||||
street = False
|
||||
zipcode = False
|
||||
street = False
|
||||
before = False
|
||||
if self.remote_owner.startswith('KN: '):
|
||||
self.reference = self.remote_owner[4:]
|
||||
@@ -212,8 +210,7 @@ class transaction(models.mem_bank_transaction):
|
||||
elif not self.execution_date:
|
||||
self.error_message = "No execution date"
|
||||
elif not self.remote_account and self.transfer_type not in [
|
||||
'BA', 'FL', 'GM', 'IC', 'PK', 'ST'
|
||||
]:
|
||||
'BA', 'FL', 'GM', 'IC', 'PK', 'ST']:
|
||||
self.error_message = (
|
||||
"No remote account for transaction type %s" %
|
||||
self.transfer_type)
|
||||
@@ -225,6 +222,7 @@ class transaction(models.mem_bank_transaction):
|
||||
No processing done here for Triodos, maybe later.
|
||||
'''
|
||||
|
||||
|
||||
class statement(models.mem_bank_statement):
|
||||
'''
|
||||
Implementation of bank_statement communication class of account_banking
|
||||
@@ -240,7 +238,7 @@ class statement(models.mem_bank_statement):
|
||||
self.date = str2date(msg.date, '%Y%m%d')
|
||||
except ValueError:
|
||||
self.date = str2date(msg.date, '%d-%m-%Y')
|
||||
self.start_balance = self.end_balance = 0 # msg.start_balance
|
||||
self.start_balance = self.end_balance = 0 # msg.start_balance
|
||||
self.import_transaction(msg)
|
||||
|
||||
def import_transaction(self, msg):
|
||||
@@ -251,6 +249,7 @@ class statement(models.mem_bank_statement):
|
||||
self.end_balance += trans.transferred_amount
|
||||
self.transactions.append(trans)
|
||||
|
||||
|
||||
class parser(models.parser):
|
||||
code = 'ING'
|
||||
country_code = 'NL'
|
||||
@@ -287,5 +286,3 @@ Statements.
|
||||
stmnt = statement(msg)
|
||||
result.append(stmnt)
|
||||
return result
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -19,19 +19,19 @@
|
||||
#
|
||||
##############################################################################
|
||||
{
|
||||
"name" : "MT940 import for Dutch ING",
|
||||
"version" : "1.1",
|
||||
"author" : "Therp BV",
|
||||
"name": "MT940 import for Dutch ING",
|
||||
"version": "1.1",
|
||||
"author": "Therp BV",
|
||||
"complexity": "normal",
|
||||
"description": """
|
||||
This addon imports the structured MT940 format as offered by the Dutch ING
|
||||
bank.
|
||||
""",
|
||||
"category" : "Account Banking",
|
||||
"depends" : [
|
||||
"category": "Account Banking",
|
||||
"depends": [
|
||||
'account_banking_mt940',
|
||||
],
|
||||
"data" : [
|
||||
"data": [
|
||||
],
|
||||
"js": [
|
||||
],
|
||||
@@ -42,7 +42,7 @@ bank.
|
||||
"auto_install": False,
|
||||
"installable": True,
|
||||
"application": False,
|
||||
"external_dependencies" : {
|
||||
'python' : [],
|
||||
"external_dependencies": {
|
||||
'python': [],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -20,8 +20,10 @@
|
||||
##############################################################################
|
||||
import re
|
||||
from openerp.tools.translate import _
|
||||
from openerp.addons.account_banking.parsers.models import parser,\
|
||||
mem_bank_transaction
|
||||
from openerp.addons.account_banking.parsers.models import (
|
||||
parser,
|
||||
mem_bank_transaction,
|
||||
)
|
||||
from openerp.addons.account_banking_mt940.mt940 import MT940, str2float
|
||||
|
||||
|
||||
@@ -30,6 +32,7 @@ class transaction(mem_bank_transaction):
|
||||
'''allow transactions without remote account'''
|
||||
return bool(self.execution_date) and bool(self.transferred_amount)
|
||||
|
||||
|
||||
class IngMT940Parser(MT940, parser):
|
||||
name = _('ING MT940 (structured)')
|
||||
country_code = 'NL'
|
||||
@@ -104,5 +107,5 @@ class IngMT940Parser(MT940, parser):
|
||||
|
||||
if not subfields:
|
||||
self.current_transaction.message = data
|
||||
|
||||
|
||||
self.current_transaction = None
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
# garantees and support are strongly adviced to contract EduSense BV
|
||||
#
|
||||
# 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,
|
||||
@@ -24,6 +24,5 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
import multibank
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
from . import multibank
|
||||
|
||||
@@ -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,
|
||||
@@ -37,6 +37,7 @@ __all__ = ['parser']
|
||||
|
||||
bt = models.mem_bank_transaction
|
||||
|
||||
|
||||
class transaction_message(object):
|
||||
'''
|
||||
A auxiliary class to validate and coerce read values
|
||||
@@ -60,7 +61,7 @@ class transaction_message(object):
|
||||
Banking Tools regulations, it is considered to be used by all banks
|
||||
in the Netherlands which comply to it. If not, please notify us.
|
||||
'''
|
||||
if len(accountno) == 10: # Invalid: longest number is 9
|
||||
if len(accountno) == 10: # Invalid: longest number is 9
|
||||
accountno = accountno[1:]
|
||||
# 9-scheme or 7-scheme?
|
||||
stripped = accountno.lstrip('0')
|
||||
@@ -73,18 +74,18 @@ class transaction_message(object):
|
||||
Initialize own dict with attributes and coerce values to right type
|
||||
'''
|
||||
if len(self.attrnames) != len(values):
|
||||
raise ValueError, \
|
||||
_('Invalid transaction line: expected %d columns, found %d') \
|
||||
% (len(self.attrnames), len(values))
|
||||
raise ValueError(
|
||||
_('Invalid transaction line: expected %d columns, found %d')
|
||||
% (len(self.attrnames), len(values))
|
||||
)
|
||||
self.__dict__.update(dict(zip(self.attrnames, values)))
|
||||
#self.local_account = self.clean_account(self.local_account)
|
||||
#self.remote_account = self.clean_account(self.remote_account)
|
||||
self.start_balance = float(self.start_balance)
|
||||
self.transferred_amount = float(self.transferred_amount)
|
||||
self.execution_date = str2date(self.execution_date, '%d-%m-%Y')
|
||||
self.value_date = str2date(self.value_date, '%d-%m-%Y')
|
||||
self.id = str(subno).zfill(4)
|
||||
|
||||
|
||||
class transaction(models.mem_bank_transaction):
|
||||
'''
|
||||
Implementation of transaction communication class for account_banking.
|
||||
@@ -93,7 +94,7 @@ class transaction(models.mem_bank_transaction):
|
||||
'remote_owner', 'remote_currency', 'transferred_amount',
|
||||
'execution_date', 'value_date', 'transfer_type',
|
||||
'reference', 'message', 'statement_id', 'id',
|
||||
]
|
||||
]
|
||||
|
||||
type_map = {
|
||||
'ACC': bt.ORDER,
|
||||
@@ -151,14 +152,12 @@ class transaction(models.mem_bank_transaction):
|
||||
two accounts - the cash exits the banking system. These withdrawals
|
||||
have their transfer_type set to 'OPN'.
|
||||
'''
|
||||
return (self.transferred_amount and self.execution_date and
|
||||
self.value_date) and (
|
||||
self.remote_account or
|
||||
self.transfer_type in [
|
||||
'KST', 'PRV', 'BTL', 'BEA', 'OPN', 'KNT', 'DIV',
|
||||
]
|
||||
and not self.error_message
|
||||
)
|
||||
return ((
|
||||
self.transferred_amount and self.execution_date
|
||||
and self.value_date)
|
||||
and (self.remote_account or self.transfer_type in [
|
||||
'KST', 'PRV', 'BTL', 'BEA', 'OPN', 'KNT', 'DIV'
|
||||
] and not self.error_message))
|
||||
|
||||
def parse_message(self):
|
||||
'''
|
||||
@@ -172,7 +171,7 @@ class transaction(models.mem_bank_transaction):
|
||||
|
||||
elif self.transfer_type == 'BEA':
|
||||
# Payment through payment terminal
|
||||
# Remote owner is part of message, while remote_owner is set
|
||||
# Remote owner is part of message, while remote_owner is set
|
||||
# to the intermediate party, which we don't need.
|
||||
self.remote_owner = self.message[:23].rstrip()
|
||||
self.remote_owner_city = self.message[23:31].rstrip()
|
||||
@@ -188,8 +187,10 @@ class transaction(models.mem_bank_transaction):
|
||||
# The ordered transferred amount
|
||||
currency, amount = part.split('. ')[1].split()
|
||||
if self.remote_currency != currency.upper():
|
||||
self.error_message = \
|
||||
'Remote currency in message differs from transaction.'
|
||||
self.error_message = (
|
||||
'Remote currency in message differs from '
|
||||
'transaction.'
|
||||
)
|
||||
else:
|
||||
self.local_amount = float(amount)
|
||||
elif part.startswith('koers. '):
|
||||
@@ -262,6 +263,7 @@ class transaction(models.mem_bank_transaction):
|
||||
parts = parts[:-1]
|
||||
self.message = ' '.join(parts)
|
||||
|
||||
|
||||
class statement(models.mem_bank_statement):
|
||||
'''
|
||||
Implementation of bank_statement communication class of account_banking
|
||||
@@ -285,6 +287,7 @@ class statement(models.mem_bank_statement):
|
||||
self.end_balance += trans.transferred_amount
|
||||
self.transactions.append(trans)
|
||||
|
||||
|
||||
class parser(models.parser):
|
||||
code = 'NLBT'
|
||||
country_code = 'NL'
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
# or Therp BV
|
||||
#
|
||||
# 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,
|
||||
@@ -26,6 +26,5 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
import triodos
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
from . import triodos
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
@@ -28,10 +28,9 @@ Dutch Banking Tools uses the concept of 'Afschrift' or Bank Statement.
|
||||
Every transaction is bound to a Bank Statement. As such, this module generates
|
||||
Bank Statements along with Bank Transactions.
|
||||
'''
|
||||
from datetime import datetime
|
||||
|
||||
from account_banking.parsers import models
|
||||
from account_banking.parsers.convert import str2date
|
||||
from account_banking.sepa import postalcode
|
||||
from tools.translate import _
|
||||
|
||||
import re
|
||||
@@ -41,6 +40,7 @@ __all__ = ['parser']
|
||||
|
||||
bt = models.mem_bank_transaction
|
||||
|
||||
|
||||
class transaction_message(object):
|
||||
'''
|
||||
A auxiliary class to validate and coerce read values
|
||||
@@ -72,50 +72,57 @@ class transaction_message(object):
|
||||
self.remote_account = self.remote_account.replace('.', '').zfill(10)
|
||||
self.local_account = self.local_account.replace('.', '').zfill(10)
|
||||
|
||||
|
||||
class transaction(models.mem_bank_transaction):
|
||||
'''
|
||||
Implementation of transaction communication class for account_banking.
|
||||
'''
|
||||
attrnames = ['local_account', 'remote_account',
|
||||
'remote_owner', 'transferred_amount',
|
||||
'execution_date', 'value_date', 'transfer_type',
|
||||
'reference', 'id',
|
||||
]
|
||||
attrnames = [
|
||||
'local_account',
|
||||
'remote_account',
|
||||
'remote_owner',
|
||||
'transferred_amount',
|
||||
'execution_date',
|
||||
'value_date',
|
||||
'transfer_type',
|
||||
'reference',
|
||||
'id',
|
||||
]
|
||||
|
||||
type_map = {
|
||||
# retrieved from online help in the Triodos banking application
|
||||
'AC': bt.ORDER, # Acceptgiro gecodeerd
|
||||
'AN': bt.ORDER, # Acceptgiro ongecodeerd
|
||||
'AT': bt.ORDER, # Acceptgiro via internet
|
||||
'BA': bt.PAYMENT_TERMINAL, # Betaalautomaat
|
||||
'CHIP': bt.BANK_TERMINAL, # Chipknip
|
||||
# 'CO': # Correctie
|
||||
'DB': bt.ORDER, # Diskettebetaling
|
||||
# 'DV': # Dividend
|
||||
'EI': bt.DIRECT_DEBIT, # Europese Incasso
|
||||
'EICO': bt.DIRECT_DEBIT, # Europese Incasso Correctie
|
||||
'EIST': bt.ORDER, # Europese Incasso Storno
|
||||
'ET': bt.ORDER, # Europese Transactie
|
||||
'ETST': bt.ORDER, #Europese Transactie Storno
|
||||
'GA': bt.BANK_TERMINAL, # Geldautomaat
|
||||
'IB': bt.ORDER, # Interne Boeking
|
||||
'IC': bt.DIRECT_DEBIT, # Incasso
|
||||
'ID': bt.ORDER, # iDeal-betaling
|
||||
'IT': bt.ORDER, # Internet transactie
|
||||
'KN': bt.BANK_COSTS, # Kosten
|
||||
'KO': bt.BANK_TERMINAL, # Kasopname
|
||||
# 'KS': # Kwaliteitsstoring
|
||||
'OV': bt.ORDER, # Overboeking. NB: can also be bt.BANK_COSTS
|
||||
# when no remote_account specified!
|
||||
'PO': bt.ORDER, # Periodieke Overboeking
|
||||
'PR': bt.BANK_COSTS, # Provisie
|
||||
# 'RE': # Rente
|
||||
# 'RS': # Renteschenking
|
||||
'ST': bt.ORDER, # Storno
|
||||
'TG': bt.ORDER, # Telegiro
|
||||
# 'VL': # Vaste Lening
|
||||
'VO': bt.DIRECT_DEBIT, # Vordering overheid
|
||||
'VV': bt.ORDER, # Vreemde valuta
|
||||
'AC': bt.ORDER, # Acceptgiro gecodeerd
|
||||
'AN': bt.ORDER, # Acceptgiro ongecodeerd
|
||||
'AT': bt.ORDER, # Acceptgiro via internet
|
||||
'BA': bt.PAYMENT_TERMINAL, # Betaalautomaat
|
||||
'CHIP': bt.BANK_TERMINAL, # Chipknip
|
||||
# 'CO': # Correctie
|
||||
'DB': bt.ORDER, # Diskettebetaling
|
||||
# 'DV': # Dividend
|
||||
'EI': bt.DIRECT_DEBIT, # Europese Incasso
|
||||
'EICO': bt.DIRECT_DEBIT, # Europese Incasso Correctie
|
||||
'EIST': bt.ORDER, # Europese Incasso Storno
|
||||
'ET': bt.ORDER, # Europese Transactie
|
||||
'ETST': bt.ORDER, # Europese Transactie Storno
|
||||
'GA': bt.BANK_TERMINAL, # Geldautomaat
|
||||
'IB': bt.ORDER, # Interne Boeking
|
||||
'IC': bt.DIRECT_DEBIT, # Incasso
|
||||
'ID': bt.ORDER, # iDeal-betaling
|
||||
'IT': bt.ORDER, # Internet transactie
|
||||
'KN': bt.BANK_COSTS, # Kosten
|
||||
'KO': bt.BANK_TERMINAL, # Kasopname
|
||||
# 'KS': # Kwaliteitsstoring
|
||||
'OV': bt.ORDER, # Overboeking. NB: can also be bt.BANK_COSTS
|
||||
# when no remote_account specified!
|
||||
'PO': bt.ORDER, # Periodieke Overboeking
|
||||
'PR': bt.BANK_COSTS, # Provisie
|
||||
# 'RE': # Rente
|
||||
# 'RS': # Renteschenking
|
||||
'ST': bt.ORDER, # Storno
|
||||
'TG': bt.ORDER, # Telegiro
|
||||
# 'VL': # Vaste Lening
|
||||
'VO': bt.DIRECT_DEBIT, # Vordering overheid
|
||||
'VV': bt.ORDER, # Vreemde valuta
|
||||
}
|
||||
|
||||
def __init__(self, line, *args, **kwargs):
|
||||
@@ -129,9 +136,9 @@ class transaction(models.mem_bank_transaction):
|
||||
self.message = ''
|
||||
# Decompose structured messages
|
||||
self.parse_message()
|
||||
if (self.transfer_type == 'OV' and
|
||||
not self.remote_account and
|
||||
not self.remote_owner):
|
||||
if (self.transfer_type == 'OV'
|
||||
and not self.remote_account
|
||||
and not self.remote_owner):
|
||||
self.transfer_type = 'KN'
|
||||
|
||||
def is_valid(self):
|
||||
@@ -141,8 +148,7 @@ class transaction(models.mem_bank_transaction):
|
||||
elif not self.execution_date:
|
||||
self.error_message = "No execution date"
|
||||
elif not self.remote_account and self.transfer_type not in [
|
||||
'KN', 'TG', 'GA', 'BA', 'CHIP'
|
||||
]:
|
||||
'KN', 'TG', 'GA', 'BA', 'CHIP']:
|
||||
self.error_message = (
|
||||
"No remote account for transaction type %s" %
|
||||
self.transfer_type)
|
||||
@@ -172,7 +178,7 @@ class statement(models.mem_bank_statement):
|
||||
self.id = msg.statement_id
|
||||
self.local_account = msg.local_account
|
||||
self.date = str2date(msg.date, '%d-%m-%Y')
|
||||
self.start_balance = self.end_balance = 0 # msg.start_balance
|
||||
self.start_balance = self.end_balance = 0 # msg.start_balance
|
||||
self.import_transaction(msg)
|
||||
|
||||
def import_transaction(self, msg):
|
||||
@@ -183,6 +189,7 @@ class statement(models.mem_bank_statement):
|
||||
self.end_balance += trans.transferred_amount
|
||||
self.transactions.append(trans)
|
||||
|
||||
|
||||
class parser(models.parser):
|
||||
code = 'TRIOD'
|
||||
country_code = 'NL'
|
||||
@@ -220,5 +227,3 @@ Statements.
|
||||
stmnt = statement(msg)
|
||||
result.append(stmnt)
|
||||
return result
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -40,11 +40,16 @@
|
||||
Base module for PAIN file generation
|
||||
====================================
|
||||
|
||||
This module contains fields and functions that are used by the module for SEPA Credit Transfer (account_banking_sepa_credit_transfer) and SEPA Direct Debit (account_banking_sepa_direct_debit). This module doesn't provide any functionnality by itself.
|
||||
This module contains fields and functions that are used by the module for SEPA
|
||||
Credit Transfer (account_banking_sepa_credit_transfer) and SEPA Direct Debit
|
||||
(account_banking_sepa_direct_debit). This module doesn't provide any
|
||||
functionnality by itself.
|
||||
|
||||
This module is part of the banking addons: https://launchpad.net/banking-addons
|
||||
This module is part of the banking addons:
|
||||
https://www.github.com/OCA/banking-addons
|
||||
|
||||
This module was started during the Akretion-Noviat code sprint of November 21st 2013 in Epiais les Louvres (France).
|
||||
This module was started during the Akretion-Noviat code sprint of
|
||||
November 21st 2013 in Epiais les Louvres (France).
|
||||
''',
|
||||
'active': False,
|
||||
'installable': True,
|
||||
|
||||
@@ -50,7 +50,7 @@ class res_company(orm.Model):
|
||||
if country_code == 'BE':
|
||||
party_identifier = company_vat[2:].replace(' ', '')
|
||||
elif country_code == 'ES':
|
||||
party_identifier = company.sepa_creditor_identifier
|
||||
party_identifier = company.sepa_creditor_identifier
|
||||
return party_identifier
|
||||
|
||||
def _initiating_party_issuer_default(self, cr, uid, context=None):
|
||||
|
||||
@@ -4,8 +4,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,
|
||||
@@ -18,7 +18,8 @@
|
||||
#
|
||||
##############################################################################
|
||||
{
|
||||
'name': 'Banking Addons - Default partner journal accounts for bank transactions',
|
||||
'name': 'Banking Addons - Default partner journal accounts for bank'
|
||||
' transactions',
|
||||
'version': '0.1',
|
||||
'license': 'AGPL-3',
|
||||
'author': 'Therp BV',
|
||||
|
||||
@@ -63,6 +63,7 @@ class ResPartner(orm.Model):
|
||||
res = super(ResPartner, self).def_journal_account_bank_incr(
|
||||
cr, uid, ids, context=context)
|
||||
for partner in self.browse(cr, uid, ids, context=context):
|
||||
if partner.property_account_receivable_bank_id:
|
||||
res[partner.id] = partner.property_account_receivable_bank_id.id
|
||||
bank = partner.property_account_receivable_bank_id
|
||||
if bank:
|
||||
res[partner.id] = bank.id
|
||||
return res
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
||||
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
# All Rights Reserved
|
||||
@@ -41,7 +41,7 @@
|
||||
'workflow/account_payment.xml',
|
||||
],
|
||||
'description': '''
|
||||
This addon adds payment reconciliation infrastructure to the Banking Addons.
|
||||
This addon adds payment reconciliation infrastructure to the Banking Addons.
|
||||
|
||||
* Extends payments for digital banking:
|
||||
+ Adapted workflow in payments to reflect banking operations
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
||||
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
# All Rights Reserved
|
||||
@@ -32,14 +32,12 @@ class banking_import_line(orm.TransientModel):
|
||||
'payment_order_id': fields.many2one(
|
||||
'payment.order', 'Payment order'),
|
||||
'transaction_type': fields.selection([
|
||||
# Add payment order related transaction types
|
||||
('invoice', 'Invoice payment'),
|
||||
('payment_order_line', 'Payment from a payment order'),
|
||||
('payment_order', 'Aggregate payment order'),
|
||||
('storno', 'Canceled debit order'),
|
||||
('bank_costs', 'Bank costs'),
|
||||
('unknown', 'Unknown'),
|
||||
], 'Transaction type'),
|
||||
}
|
||||
|
||||
|
||||
# Add payment order related transaction types
|
||||
('invoice', 'Invoice payment'),
|
||||
('payment_order_line', 'Payment from a payment order'),
|
||||
('payment_order', 'Aggregate payment order'),
|
||||
('storno', 'Canceled debit order'),
|
||||
('bank_costs', 'Bank costs'),
|
||||
('unknown', 'Unknown'),
|
||||
], 'Transaction type'),
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
||||
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
# All Rights Reserved
|
||||
@@ -27,14 +27,16 @@ from openerp.osv import orm, fields
|
||||
from openerp import netsvc
|
||||
from openerp.tools.translate import _
|
||||
from openerp.addons.decimal_precision import decimal_precision as dp
|
||||
from openerp.addons.account_banking.parsers.models import mem_bank_transaction as bt
|
||||
from openerp.addons.account_banking.parsers.models import (
|
||||
mem_bank_transaction as bt
|
||||
)
|
||||
|
||||
|
||||
class banking_import_transaction(orm.Model):
|
||||
_inherit = 'banking.import.transaction'
|
||||
|
||||
def _match_payment_order(
|
||||
self, cr, uid, trans, log, order_type='payment', context=None):
|
||||
self, cr, uid, trans, log, order_type='payment', context=None):
|
||||
|
||||
def equals_order_amount(payment_order, transferred_amount):
|
||||
if (not hasattr(payment_order, 'payment_order_type')
|
||||
@@ -60,25 +62,26 @@ class banking_import_transaction(orm.Model):
|
||||
if len(candidates) > 0:
|
||||
# retrieve the common account_id, if any
|
||||
account_id = False
|
||||
if (candidates[0].line_ids[0].transit_move_line_id):
|
||||
for line in candidates[0].line_ids[0].transit_move_line_id.move_id.line_id:
|
||||
transit_move_lines = candidates[0].line_ids[0].transit_move_line_id
|
||||
if transit_move_lines:
|
||||
for line in transit_move_lines.move_id.line_id:
|
||||
if line.account_id.type == 'other':
|
||||
account_id = line.account_id.id
|
||||
break
|
||||
return dict(
|
||||
move_line_ids = False,
|
||||
match_type = 'payment_order',
|
||||
payment_order_ids = [x.id for x in candidates],
|
||||
account_id = account_id,
|
||||
partner_id = False,
|
||||
partner_bank_id = False,
|
||||
reference = False,
|
||||
move_line_ids=False,
|
||||
match_type='payment_order',
|
||||
payment_order_ids=[x.id for x in candidates],
|
||||
account_id=account_id,
|
||||
partner_id=False,
|
||||
partner_bank_id=False,
|
||||
reference=False,
|
||||
type='general',
|
||||
)
|
||||
)
|
||||
return False
|
||||
|
||||
def _match_storno(
|
||||
self, cr, uid, trans, log, context=None):
|
||||
self, cr, uid, trans, log, context=None):
|
||||
payment_line_obj = self.pool.get('payment.line')
|
||||
line_ids = payment_line_obj.search(
|
||||
cr, uid, [
|
||||
@@ -93,21 +96,21 @@ class banking_import_transaction(orm.Model):
|
||||
trans.statement_id.currency, context=None)
|
||||
if account_id:
|
||||
return dict(
|
||||
account_id = account_id,
|
||||
match_type = 'storno',
|
||||
payment_line_id = line_ids[0],
|
||||
account_id=account_id,
|
||||
match_type='storno',
|
||||
payment_line_id=line_ids[0],
|
||||
move_line_ids=False,
|
||||
partner_id=False,
|
||||
partner_bank_id=False,
|
||||
reference=False,
|
||||
type='customer',
|
||||
)
|
||||
)
|
||||
# TODO log the reason why there is no result for transfers marked
|
||||
# as storno
|
||||
return False
|
||||
|
||||
def _match_payment(self, cr, uid, trans, payment_lines,
|
||||
partner_ids, bank_account_ids, log, linked_payments):
|
||||
partner_ids, bank_account_ids, log, linked_payments):
|
||||
'''
|
||||
Find the payment order belonging to this reference - if there is one
|
||||
This is the easiest part: when sending payments, the returned bank info
|
||||
@@ -132,7 +135,7 @@ class banking_import_transaction(orm.Model):
|
||||
digits = dp.get_precision('Account')(cr)[1]
|
||||
candidates = [
|
||||
line for line in payment_lines
|
||||
if (line.communication == trans.reference
|
||||
if (line.communication == trans.reference
|
||||
and round(line.amount, digits) == -round(
|
||||
trans.statement_line_id.amount, digits)
|
||||
and bank_match(trans.remote_account, line.bank_id))
|
||||
@@ -145,15 +148,15 @@ class banking_import_transaction(orm.Model):
|
||||
move_info = self._get_move_info(
|
||||
cr, uid, [candidate.move_line_id.id])
|
||||
move_info.update({
|
||||
'match_type': 'payment',
|
||||
'payment_line_id': candidate.id,
|
||||
})
|
||||
'match_type': 'payment',
|
||||
'payment_line_id': candidate.id,
|
||||
})
|
||||
return move_info
|
||||
|
||||
return False
|
||||
|
||||
def _confirm_storno(
|
||||
self, cr, uid, transaction_id, context=None):
|
||||
self, cr, uid, transaction_id, context=None):
|
||||
"""
|
||||
Creation of the reconciliation has been delegated to
|
||||
*a* direct debit module, to allow for various direct debit styles
|
||||
@@ -167,18 +170,18 @@ class banking_import_transaction(orm.Model):
|
||||
_("No direct debit order item"))
|
||||
reconcile_id = payment_line_pool.debit_storno(
|
||||
cr, uid,
|
||||
transaction.payment_line_id.id,
|
||||
transaction.payment_line_id.id,
|
||||
transaction.statement_line_id.amount,
|
||||
transaction.statement_line_id.currency,
|
||||
transaction.storno_retry,
|
||||
context=context)
|
||||
statement_line_pool.write(
|
||||
cr, uid, transaction.statement_line_id.id,
|
||||
cr, uid, transaction.statement_line_id.id,
|
||||
{'reconcile_id': reconcile_id}, context=context)
|
||||
transaction.refresh()
|
||||
|
||||
def _confirm_payment_order(
|
||||
self, cr, uid, transaction_id, context=None):
|
||||
self, cr, uid, transaction_id, context=None):
|
||||
"""
|
||||
Creation of the reconciliation has been delegated to
|
||||
*a* direct debit module, to allow for various direct debit styles
|
||||
@@ -197,11 +200,11 @@ class banking_import_transaction(orm.Model):
|
||||
transaction.statement_line_id.currency,
|
||||
context=context)
|
||||
statement_line_pool.write(
|
||||
cr, uid, transaction.statement_line_id.id,
|
||||
cr, uid, transaction.statement_line_id.id,
|
||||
{'reconcile_id': reconcile_id}, context=context)
|
||||
|
||||
def _confirm_payment(
|
||||
self, cr, uid, transaction_id, context=None):
|
||||
self, cr, uid, transaction_id, context=None):
|
||||
"""
|
||||
Do some housekeeping on the payment line
|
||||
then pass on to _reconcile_move
|
||||
@@ -227,7 +230,7 @@ class banking_import_transaction(orm.Model):
|
||||
uid, 'payment.order', order_id, 'done', cr)
|
||||
|
||||
def _cancel_payment(
|
||||
self, cr, uid, transaction_id, context=None):
|
||||
self, cr, uid, transaction_id, context=None):
|
||||
"""
|
||||
Do not support cancelling individual lines yet, because the workflow
|
||||
of the payment order does not support reopening.
|
||||
@@ -238,7 +241,7 @@ class banking_import_transaction(orm.Model):
|
||||
"match type 'payment'"))
|
||||
|
||||
def _cancel_payment_order(
|
||||
self, cr, uid, transaction_id, context=None):
|
||||
self, cr, uid, transaction_id, context=None):
|
||||
"""
|
||||
"""
|
||||
payment_order_obj = self.pool.get('payment.order')
|
||||
@@ -259,7 +262,7 @@ class banking_import_transaction(orm.Model):
|
||||
transaction.statement_line_id.currency)
|
||||
|
||||
def _cancel_storno(
|
||||
self, cr, uid, transaction_id, context=None):
|
||||
self, cr, uid, transaction_id, context=None):
|
||||
"""
|
||||
TODO: delegate unreconciliation to the direct debit module,
|
||||
to allow for various direct debit styles
|
||||
@@ -267,7 +270,7 @@ class banking_import_transaction(orm.Model):
|
||||
payment_line_obj = self.pool.get('payment.line')
|
||||
reconcile_obj = self.pool.get('account.move.reconcile')
|
||||
transaction = self.browse(cr, uid, transaction_id, context=context)
|
||||
|
||||
|
||||
if not transaction.payment_line_id:
|
||||
raise orm.except_orm(
|
||||
_("Cannot cancel link with storno"),
|
||||
@@ -295,7 +298,8 @@ class banking_import_transaction(orm.Model):
|
||||
raise orm.except_orm(
|
||||
_("Cannot cancel link with storno"),
|
||||
_("Line id not found"))
|
||||
reconcile = cancel_line.reconcile_id or cancel_line.reconcile_partial_id
|
||||
reconcile = (cancel_line.reconcile_id
|
||||
or cancel_line.reconcile_partial_id)
|
||||
lines_reconcile = reconcile.line_id or reconcile.line_partial_ids
|
||||
if len(lines_reconcile) < 3:
|
||||
# delete the full reconciliation
|
||||
@@ -303,15 +307,15 @@ class banking_import_transaction(orm.Model):
|
||||
else:
|
||||
# we are left with a partial reconciliation
|
||||
reconcile_obj.write(
|
||||
cr, uid, reconcile.id,
|
||||
{'line_partial_ids':
|
||||
cr, uid, reconcile.id,
|
||||
{'line_partial_ids':
|
||||
[(6, 0, [x.id for x in lines_reconcile
|
||||
if x.id != cancel_line.id])],
|
||||
'line_id': [(6, 0, [])],
|
||||
}, context)
|
||||
# redo the original payment line reconciliation with the invoice
|
||||
payment_line_obj.write(
|
||||
cr, uid, transaction.payment_line_id.id,
|
||||
cr, uid, transaction.payment_line_id.id,
|
||||
{'storno': False}, context)
|
||||
payment_line_obj.debit_reconcile(
|
||||
cr, uid, transaction.payment_line_id.id, context)
|
||||
@@ -333,7 +337,7 @@ class banking_import_transaction(orm.Model):
|
||||
for transaction in self.browse(cr, uid, ids, context):
|
||||
if transaction.match_type == 'payment_order':
|
||||
if (transaction.payment_order_ids and not
|
||||
transaction.payment_order_id):
|
||||
transaction.payment_order_id):
|
||||
res[transaction.id] = True
|
||||
return res
|
||||
|
||||
@@ -354,7 +358,7 @@ class banking_import_transaction(orm.Model):
|
||||
vals['payment_order_ids'] = [
|
||||
(6, 0, move_info.get('payment_order_ids') or [])]
|
||||
vals['payment_order_id'] = (
|
||||
move_info.get('payment_order_ids', False) and
|
||||
move_info.get('payment_order_ids', False) and
|
||||
len(move_info['payment_order_ids']) == 1 and
|
||||
move_info['payment_order_ids'][0]
|
||||
)
|
||||
@@ -388,17 +392,19 @@ class banking_import_transaction(orm.Model):
|
||||
super(banking_import_transaction, self).__init__(pool, cr)
|
||||
|
||||
self.confirm_map.update({
|
||||
'storno': banking_import_transaction._confirm_storno,
|
||||
'payment_order': banking_import_transaction._confirm_payment_order,
|
||||
'payment': banking_import_transaction._confirm_payment,
|
||||
'payment_order_manual': banking_import_transaction._confirm_payment_order,
|
||||
'payment_manual': banking_import_transaction._confirm_payment,
|
||||
})
|
||||
'storno': banking_import_transaction._confirm_storno,
|
||||
'payment_order': banking_import_transaction._confirm_payment_order,
|
||||
'payment': banking_import_transaction._confirm_payment,
|
||||
'payment_order_manual': (
|
||||
banking_import_transaction._confirm_payment_order),
|
||||
'payment_manual': banking_import_transaction._confirm_payment,
|
||||
})
|
||||
|
||||
self.cancel_map.update({
|
||||
'storno': banking_import_transaction._cancel_storno,
|
||||
'payment_order': banking_import_transaction._cancel_payment_order,
|
||||
'payment': banking_import_transaction._cancel_payment,
|
||||
'payment_order_manual': banking_import_transaction._cancel_payment_order,
|
||||
'payment_manual': banking_import_transaction._cancel_payment,
|
||||
})
|
||||
'storno': banking_import_transaction._cancel_storno,
|
||||
'payment_order': banking_import_transaction._cancel_payment_order,
|
||||
'payment': banking_import_transaction._cancel_payment,
|
||||
'payment_order_manual': (
|
||||
banking_import_transaction._cancel_payment_order),
|
||||
'payment_manual': banking_import_transaction._cancel_payment,
|
||||
})
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
||||
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
# All Rights Reserved
|
||||
@@ -53,19 +53,23 @@ class banking_transaction_wizard(orm.TransientModel):
|
||||
sign = 1
|
||||
else:
|
||||
sign = -1
|
||||
total = (payment_order.total + sign *
|
||||
total = (payment_order.total + sign *
|
||||
transaction_id.statement_line_id.amount)
|
||||
if not self.pool.get('res.currency').is_zero(
|
||||
cr, uid, transaction_id.statement_line_id.statement_id.currency, total):
|
||||
cr, uid,
|
||||
transaction_id.statement_line_id.statement_id.currency,
|
||||
total):
|
||||
raise orm.except_orm(
|
||||
_('Error'),
|
||||
_('When matching a payment order, the amounts have to '
|
||||
'match exactly'))
|
||||
|
||||
if payment_order.mode and payment_order.mode.transfer_account_id:
|
||||
|
||||
if (payment_order.mode
|
||||
and payment_order.mode.transfer_account_id):
|
||||
transaction_id.statement_line_id.write({
|
||||
'account_id': payment_order.mode.transfer_account_id.id,
|
||||
})
|
||||
'account_id': (
|
||||
payment_order.mode.transfer_account_id.id),
|
||||
})
|
||||
write_vals.update(
|
||||
{'payment_order_id': manual_payment_order_id,
|
||||
'match_type': 'payment_order_manual'})
|
||||
@@ -79,25 +83,40 @@ class banking_transaction_wizard(orm.TransientModel):
|
||||
|
||||
_columns = {
|
||||
'payment_line_id': fields.related(
|
||||
'import_transaction_id', 'payment_line_id',
|
||||
string="Matching payment or storno",
|
||||
type='many2one', relation='payment.line',
|
||||
readonly=True),
|
||||
'import_transaction_id',
|
||||
'payment_line_id',
|
||||
string="Matching payment or storno",
|
||||
type='many2one',
|
||||
relation='payment.line',
|
||||
readonly=True,
|
||||
),
|
||||
'payment_order_ids': fields.related(
|
||||
'import_transaction_id', 'payment_order_ids',
|
||||
string="Matching payment orders",
|
||||
type='many2many', relation='payment.order'),
|
||||
'import_transaction_id',
|
||||
'payment_order_ids',
|
||||
string="Matching payment orders",
|
||||
type='many2many',
|
||||
relation='payment.order',
|
||||
),
|
||||
'payment_order_id': fields.related(
|
||||
'import_transaction_id', 'payment_order_id',
|
||||
string="Payment order to reconcile",
|
||||
type='many2one', relation='payment.order'),
|
||||
'import_transaction_id',
|
||||
'payment_order_id',
|
||||
string="Payment order to reconcile",
|
||||
type='many2one',
|
||||
relation='payment.order',
|
||||
),
|
||||
'manual_payment_order_id': fields.many2one(
|
||||
'payment.order', 'Match this payment order',
|
||||
domain=[('state', '=', 'sent')]),
|
||||
'payment.order',
|
||||
'Match this payment order',
|
||||
domain=[
|
||||
('state', '=', 'sent'),
|
||||
],
|
||||
),
|
||||
'manual_payment_line_id': fields.many2one(
|
||||
'payment.line', 'Match this payment line',
|
||||
'payment.line',
|
||||
'Match this payment line',
|
||||
domain=[
|
||||
('order_id.state', '=', 'sent'),
|
||||
('date_done', '=', False),
|
||||
]),
|
||||
}
|
||||
],
|
||||
),
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
||||
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
# All Rights Reserved
|
||||
@@ -27,6 +27,7 @@ from openerp.osv import orm, fields
|
||||
from openerp import netsvc
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
||||
class payment_line(orm.Model):
|
||||
'''
|
||||
Add some fields; make destination bank account
|
||||
@@ -40,7 +41,7 @@ class payment_line(orm.Model):
|
||||
'date_done': fields.date(
|
||||
'Date Confirmed', select=True, readonly=True),
|
||||
'transit_move_line_id': fields.many2one(
|
||||
# this line is part of the credit side of move 2a
|
||||
# this line is part of the credit side of move 2a
|
||||
# from the documentation
|
||||
'account.move.line', 'Debit move line',
|
||||
readonly=True,
|
||||
@@ -57,7 +58,7 @@ class payment_line(orm.Model):
|
||||
account_direct_debit module.
|
||||
"""
|
||||
def get_storno_account_id(self, cr, uid, payment_line_id, amount,
|
||||
currency_id, context=None):
|
||||
currency_id, context=None):
|
||||
"""
|
||||
Hook for verifying a match of the payment line with the amount.
|
||||
Return the account associated with the storno.
|
||||
@@ -115,19 +116,21 @@ class payment_line(orm.Model):
|
||||
if (not transit_move_line or not torec_move_line):
|
||||
raise orm.except_orm(
|
||||
_('Can not reconcile'),
|
||||
_('No move line for line %s') % payment_line.name)
|
||||
if torec_move_line.reconcile_id: # torec_move_line.reconcile_partial_id:
|
||||
_('No move line for line %s') % payment_line.name
|
||||
)
|
||||
if torec_move_line.reconcile_id:
|
||||
raise orm.except_orm(
|
||||
_('Error'),
|
||||
_('Move line %s has already been reconciled') %
|
||||
_('Move line %s has already been reconciled') %
|
||||
torec_move_line.name
|
||||
)
|
||||
if transit_move_line.reconcile_id or transit_move_line.reconcile_partial_id:
|
||||
if (transit_move_line.reconcile_id
|
||||
or transit_move_line.reconcile_partial_id):
|
||||
raise orm.except_orm(
|
||||
_('Error'),
|
||||
_('Move line %s has already been reconciled') %
|
||||
_('Move line %s has already been reconciled') %
|
||||
transit_move_line.name
|
||||
)
|
||||
)
|
||||
|
||||
def is_zero(total):
|
||||
return self.pool.get('res.currency').is_zero(
|
||||
@@ -136,7 +139,7 @@ class payment_line(orm.Model):
|
||||
line_ids = [transit_move_line.id, torec_move_line.id]
|
||||
if torec_move_line.reconcile_partial_id:
|
||||
line_ids = [
|
||||
x.id for x in
|
||||
x.id for x in
|
||||
torec_move_line.reconcile_partial_id.line_partial_ids
|
||||
] + [transit_move_line.id]
|
||||
|
||||
@@ -144,7 +147,9 @@ class payment_line(orm.Model):
|
||||
vals = {
|
||||
'type': 'auto',
|
||||
'line_id': is_zero(total) and [(6, 0, line_ids)] or [(6, 0, [])],
|
||||
'line_partial_ids': is_zero(total) and [(6, 0, [])] or [(6, 0, line_ids)],
|
||||
'line_partial_ids': (is_zero(total)
|
||||
and [(6, 0, [])]
|
||||
or [(6, 0, line_ids)]),
|
||||
}
|
||||
|
||||
if torec_move_line.reconcile_partial_id:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
||||
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
# All Rights Reserved
|
||||
@@ -44,7 +44,7 @@ class payment_mode(orm.Model):
|
||||
'a debit order of this mode'),
|
||||
),
|
||||
'payment_term_ids': fields.many2many(
|
||||
'account.payment.term', 'account_payment_order_terms_rel',
|
||||
'account.payment.term', 'account_payment_order_terms_rel',
|
||||
'mode_id', 'term_id', 'Payment terms',
|
||||
help=('Limit selected invoices to invoices with these payment '
|
||||
'terms')
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
||||
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
|
||||
# (C) 2014 ACSONE SA/NV (<http://acsone.eu>).
|
||||
#
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
# All Rights Reserved
|
||||
|
||||
@@ -1 +1 @@
|
||||
from . import model
|
||||
from . import model
|
||||
|
||||
@@ -56,8 +56,8 @@
|
||||
],
|
||||
'demo': ['demo/banking_demo.xml'],
|
||||
'description': '''
|
||||
Infrastructure to export payment orders
|
||||
plus some bug fixes and obvious enhancements to payment orders
|
||||
Infrastructure to export payment orders
|
||||
plus some bug fixes and obvious enhancements to payment orders
|
||||
that will hopefully land in offical addons one day.
|
||||
|
||||
This technical module provides the base infrastructure to export
|
||||
@@ -65,21 +65,25 @@
|
||||
technical features:
|
||||
* a new payment.mode.type model
|
||||
* payment.mode now has a mandatory type
|
||||
* a better implementation of payment_mode.suitable_bank_types() based on payment.mode.type
|
||||
* the "make payment" button launches a wizard depending on the payment.mode.type
|
||||
* a manual payment mode type is provided as an example, with a default "do nothing" wizard
|
||||
* a better implementation of payment_mode.suitable_bank_types() based
|
||||
on payment.mode.type
|
||||
* the "make payment" button launches a wizard depending on the
|
||||
payment.mode.type
|
||||
* a manual payment mode type is provided as an example, with a default
|
||||
"do nothing" wizard
|
||||
|
||||
To enable the use of payment order to collect money for customers,
|
||||
it adds a payment_order_type (payment|debit) as a basis of direct debit support
|
||||
(this field becomes visible when account_direct_debit is installed).
|
||||
Refactoring note: this field should ideally go in account_direct_debit,
|
||||
but account_banking_payment currently depends on it.
|
||||
To enable the use of payment order to collect money for customers,
|
||||
it adds a payment_order_type (payment|debit) as a basis of direct debit
|
||||
support (this field becomes visible when account_direct_debit is
|
||||
installed).
|
||||
Refactoring note: this field should ideally go in account_direct_debit,
|
||||
but account_banking_payment currently depends on it.
|
||||
|
||||
Bug fixes and enhancement that should land in official addons:
|
||||
* make the search function of the payment export wizard extensible
|
||||
* fix lp:1275478: allow payment of customer refunds
|
||||
* display the maturity date of the move lines when you are in
|
||||
* display the maturity date of the move lines when you are in
|
||||
the wizard to select the lines to pay
|
||||
''',
|
||||
''',
|
||||
'installable': True,
|
||||
}
|
||||
|
||||
@@ -73,15 +73,18 @@ class account_move_line(orm.Model):
|
||||
) %(operator)s %%s ''' % {'operator': x[1]}, args))
|
||||
sql_args = tuple(map(itemgetter(2), args))
|
||||
|
||||
cr.execute(('''SELECT id
|
||||
cr.execute(
|
||||
('''\
|
||||
SELECT id
|
||||
FROM account_move_line l
|
||||
WHERE account_id IN (select id
|
||||
FROM account_account
|
||||
WHERE type in %s AND active)
|
||||
AND reconcile_id IS null
|
||||
AND credit > 0
|
||||
AND ''' + where + ' and ' + query),
|
||||
(('payable', 'receivable'),)+sql_args )
|
||||
AND ''' + where + ' and ' + query
|
||||
), (('payable', 'receivable'), ) + sql_args
|
||||
)
|
||||
# The patch we have compared to the original function in
|
||||
# addons/account_payment is just above :
|
||||
# original code : type = 'payable'
|
||||
@@ -93,6 +96,10 @@ class account_move_line(orm.Model):
|
||||
return [('id', 'in', map(lambda x:x[0], res))]
|
||||
|
||||
_columns = {
|
||||
'amount_to_pay': fields.function(amount_to_pay,
|
||||
type='float', string='Amount to pay', fnct_search=_to_pay_search),
|
||||
'amount_to_pay': fields.function(
|
||||
amount_to_pay,
|
||||
type='float',
|
||||
string='Amount to pay',
|
||||
fnct_search=_to_pay_search
|
||||
),
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
||||
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
# All Rights Reserved
|
||||
@@ -52,7 +52,7 @@ class payment_order(orm.Model):
|
||||
If type is manual. just confirm the order.
|
||||
Previously (pre-v6) in account_payment/wizard/wizard_pay.py
|
||||
"""
|
||||
if context == None:
|
||||
if context is None:
|
||||
context = {}
|
||||
result = {}
|
||||
orders = self.browse(cr, uid, ids, context)
|
||||
@@ -81,10 +81,13 @@ class payment_order(orm.Model):
|
||||
if order.mode.type and order.mode.type.ir_model_id:
|
||||
raise orm.except_orm(
|
||||
_('Error'),
|
||||
_('You can only combine payment orders of the same type')
|
||||
)
|
||||
_('You can only combine payment orders of the same '
|
||||
'type')
|
||||
)
|
||||
# process manual payments
|
||||
wf_service = netsvc.LocalService('workflow')
|
||||
for order_id in ids:
|
||||
wf_service.trg_validate(uid, 'payment.order', order_id, 'done', cr)
|
||||
wf_service.trg_validate(
|
||||
uid, 'payment.order', order_id, 'done', cr
|
||||
)
|
||||
return result
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
||||
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
# All Rights Reserved
|
||||
@@ -37,18 +37,24 @@ class payment_manual(orm.TransientModel):
|
||||
_description = 'Send payment order(s) manually'
|
||||
|
||||
_columns = {
|
||||
'payment_order_ids': fields.many2many('payment.order',
|
||||
'wiz_manual_payorders_rel', 'wizard_id', 'payment_order_id',
|
||||
'Payment orders', readonly=True),
|
||||
}
|
||||
'payment_order_ids': fields.many2many(
|
||||
'payment.order',
|
||||
'wiz_manual_payorders_rel',
|
||||
'wizard_id',
|
||||
'payment_order_id',
|
||||
'Payment orders',
|
||||
readonly=True
|
||||
),
|
||||
}
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
payment_order_ids = context.get('active_ids', [])
|
||||
vals.update({
|
||||
'payment_order_ids': [[6, 0, payment_order_ids]],
|
||||
})
|
||||
return super(payment_manual, self).create(cr, uid,
|
||||
vals, context=context)
|
||||
return super(payment_manual, self).create(
|
||||
cr, uid, vals, context=context
|
||||
)
|
||||
|
||||
def button_ok(self, cr, uid, ids, context=None):
|
||||
wf_service = netsvc.LocalService('workflow')
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
||||
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
# All Rights Reserved
|
||||
@@ -38,8 +38,9 @@ class payment_mode(orm.Model):
|
||||
res = []
|
||||
payment_mode = self.browse(
|
||||
cr, uid, payment_mode_id, context)
|
||||
if (payment_mode and payment_mode.type and
|
||||
payment_mode.type.suitable_bank_types):
|
||||
if (payment_mode
|
||||
and payment_mode.type
|
||||
and payment_mode.type.suitable_bank_types):
|
||||
res = [t.code for t in payment_mode.type.suitable_bank_types]
|
||||
return res
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
||||
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
# All Rights Reserved
|
||||
@@ -63,9 +63,11 @@ class payment_mode_type(orm.Model):
|
||||
|
||||
def _auto_init(self, cr, context=None):
|
||||
r = super(payment_mode_type, self)._auto_init(cr, context=context)
|
||||
# migrate xmlid from manual_bank_transfer to avoid dependency on account_banking
|
||||
cr.execute("""UPDATE ir_model_data SET module='account_banking_payment_export'
|
||||
WHERE module='account_banking' AND
|
||||
name='manual_bank_tranfer' AND
|
||||
# migrate xmlid from manual_bank_transfer to avoid dependency on
|
||||
# account_banking
|
||||
cr.execute("""UPDATE ir_model_data
|
||||
SET module='account_banking_payment_export'
|
||||
WHERE module='account_banking' AND
|
||||
name='manual_bank_tranfer' AND
|
||||
model='payment.mode.type'""")
|
||||
return r
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
||||
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
# All Rights Reserved
|
||||
@@ -50,8 +50,8 @@ class payment_order_create(orm.TransientModel):
|
||||
context = {}
|
||||
data = self.read(cr, uid, ids, ['duedate'], context=context)[0]
|
||||
search_due_date = data['duedate']
|
||||
|
||||
### start account_banking_payment ###
|
||||
|
||||
# start account_banking_payment
|
||||
payment = self.pool.get('payment.order').browse(
|
||||
cr, uid, context['active_id'], context=context)
|
||||
# Search for move line to pay:
|
||||
@@ -62,7 +62,7 @@ class payment_order_create(orm.TransientModel):
|
||||
]
|
||||
self.extend_payment_order_domain(
|
||||
cr, uid, payment, domain, context=context)
|
||||
### end account_direct_debit ###
|
||||
# end account_direct_debit
|
||||
|
||||
domain = domain + [
|
||||
'|', ('date_maturity', '<=', search_due_date),
|
||||
@@ -71,21 +71,22 @@ class payment_order_create(orm.TransientModel):
|
||||
line_ids = line_obj.search(cr, uid, domain, context=context)
|
||||
context.update({'line_ids': line_ids})
|
||||
model_data_ids = mod_obj.search(
|
||||
cr, uid,[
|
||||
cr, uid, [
|
||||
('model', '=', 'ir.ui.view'),
|
||||
('name', '=', 'view_create_payment_order_lines')],
|
||||
context=context)
|
||||
resource_id = mod_obj.read(
|
||||
cr, uid, model_data_ids, fields=['res_id'],
|
||||
context=context)[0]['res_id']
|
||||
return {'name': _('Entry Lines'),
|
||||
'context': context,
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'payment.order.create',
|
||||
'views': [(resource_id, 'form')],
|
||||
'type': 'ir.actions.act_window',
|
||||
'target': 'new',
|
||||
return {
|
||||
'name': _('Entry Lines'),
|
||||
'context': context,
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'payment.order.create',
|
||||
'views': [(resource_id, 'form')],
|
||||
'type': 'ir.actions.act_window',
|
||||
'target': 'new',
|
||||
}
|
||||
|
||||
def _prepare_payment_line(self, cr, uid, payment, line, context=None):
|
||||
@@ -93,26 +94,26 @@ class payment_order_create(orm.TransientModel):
|
||||
The resulting dict is passed to the create method of payment.line'''
|
||||
_today = fields.date.context_today(self, cr, uid, context=context)
|
||||
if payment.date_prefered == "now":
|
||||
#no payment date => immediate payment
|
||||
# no payment date => immediate payment
|
||||
date_to_pay = False
|
||||
elif payment.date_prefered == 'due':
|
||||
### account_banking
|
||||
# account_banking
|
||||
# date_to_pay = line.date_maturity
|
||||
date_to_pay = (
|
||||
line.date_maturity
|
||||
if line.date_maturity and line.date_maturity > _today
|
||||
else False)
|
||||
### end account banking
|
||||
# end account banking
|
||||
elif payment.date_prefered == 'fixed':
|
||||
### account_banking
|
||||
# account_banking
|
||||
# date_to_pay = payment.date_scheduled
|
||||
date_to_pay = (
|
||||
payment.date_scheduled
|
||||
if payment.date_scheduled and payment.date_scheduled > _today
|
||||
else False)
|
||||
### end account banking
|
||||
# end account banking
|
||||
|
||||
### account_banking
|
||||
# account_banking
|
||||
state = 'normal'
|
||||
communication = line.ref or '-'
|
||||
if line.invoice:
|
||||
@@ -132,19 +133,19 @@ class payment_order_create(orm.TransientModel):
|
||||
state = 'structured'
|
||||
|
||||
# support debit orders when enabled
|
||||
if (payment.payment_order_type == 'debit' and
|
||||
'amount_to_receive' in line):
|
||||
if (payment.payment_order_type == 'debit'
|
||||
and 'amount_to_receive' in line):
|
||||
amount_currency = line.amount_to_receive
|
||||
else:
|
||||
amount_currency = line.amount_to_pay
|
||||
### end account_banking
|
||||
# end account_banking
|
||||
|
||||
### account banking
|
||||
# account banking
|
||||
# t = None
|
||||
# line2bank = line_obj.line2bank(cr, uid, line_ids, t, context)
|
||||
line2bank = self.pool['account.move.line'].line2bank(
|
||||
cr, uid, [line.id], payment.mode.id, context)
|
||||
### end account banking
|
||||
# end account banking
|
||||
|
||||
res = {
|
||||
'move_line_id': line.id,
|
||||
@@ -152,11 +153,11 @@ class payment_order_create(orm.TransientModel):
|
||||
'bank_id': line2bank.get(line.id),
|
||||
'order_id': payment.id,
|
||||
'partner_id': line.partner_id and line.partner_id.id or False,
|
||||
### account banking
|
||||
# account banking
|
||||
# 'communication': line.ref or '/'
|
||||
'communication': communication,
|
||||
'state': state,
|
||||
### end account banking
|
||||
# end account banking
|
||||
'date': date_to_pay,
|
||||
'currency': (line.invoice and line.invoice.currency_id.id
|
||||
or line.journal_id.currency.id
|
||||
@@ -166,11 +167,11 @@ class payment_order_create(orm.TransientModel):
|
||||
|
||||
def create_payment(self, cr, uid, ids, context=None):
|
||||
'''
|
||||
This method is a slightly modified version of the existing method on this
|
||||
model in account_payment.
|
||||
This method is a slightly modified version of the existing method on
|
||||
this model in account_payment.
|
||||
- pass the payment mode to line2bank()
|
||||
- allow invoices to create influence on the payment process: not only 'Free'
|
||||
references are allowed, but others as well
|
||||
- allow invoices to create influence on the payment process: not only
|
||||
'Free' references are allowed, but others as well
|
||||
- check date_to_pay is not in the past.
|
||||
'''
|
||||
if context is None:
|
||||
@@ -182,18 +183,19 @@ class payment_order_create(orm.TransientModel):
|
||||
|
||||
payment = self.pool['payment.order'].browse(
|
||||
cr, uid, context['active_id'], context=context)
|
||||
## Populate the current payment with new lines:
|
||||
# Populate the current payment with new lines:
|
||||
for line in self.pool['account.move.line'].browse(
|
||||
cr, uid, line_ids, context=context):
|
||||
vals = self._prepare_payment_line(
|
||||
cr, uid, payment, line, context=context)
|
||||
self.pool['payment.line'].create(cr, uid, vals, context=context)
|
||||
# Force reload of payment order view as a workaround for lp:1155525
|
||||
return {'name': _('Payment Orders'),
|
||||
'context': context,
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form,tree',
|
||||
'res_model': 'payment.order',
|
||||
'res_id': context['active_id'],
|
||||
'type': 'ir.actions.act_window',
|
||||
return {
|
||||
'name': _('Payment Orders'),
|
||||
'context': context,
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form,tree',
|
||||
'res_model': 'payment.order',
|
||||
'res_id': context['active_id'],
|
||||
'type': 'ir.actions.act_window',
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
'depends': ['account_banking_pain_base'],
|
||||
'external_dependencies': {
|
||||
'python': ['unidecode', 'lxml'],
|
||||
},
|
||||
},
|
||||
'data': [
|
||||
'account_banking_sepa_view.xml',
|
||||
'wizard/export_sepa_view.xml',
|
||||
@@ -41,13 +41,23 @@
|
||||
'description': '''
|
||||
Module to export payment orders in SEPA XML file format.
|
||||
|
||||
SEPA PAIN (PAyment INitiation) is the new european standard for Customer-to-Bank payment instructions. This module implements SEPA Credit Transfer (SCT), more specifically PAIN versions 001.001.02, 001.001.03, 001.001.04 and 001.001.05. It is part of the ISO 20022 standard, available on http://www.iso20022.org.
|
||||
SEPA PAIN (PAyment INitiation) is the new european standard for
|
||||
Customer-to-Bank payment instructions.
|
||||
|
||||
The Implementation Guidelines for SEPA Credit Transfer published by the European Payments Council (http://http://www.europeanpaymentscouncil.eu) use PAIN version 001.001.03, so it's probably the version of PAIN that you should try first.
|
||||
This module implements SEPA Credit Transfer (SCT), more specifically PAIN
|
||||
versions 001.001.02, 001.001.03, 001.001.04 and 001.001.05.
|
||||
It is part of the ISO 20022 standard, available on http://www.iso20022.org.
|
||||
|
||||
This module uses the framework provided by the banking addons, cf https://launchpad.net/banking-addons
|
||||
The Implementation Guidelines for SEPA Credit Transfer published by the
|
||||
European Payments Council (http://http://www.europeanpaymentscouncil.eu)
|
||||
use PAIN version 001.001.03, so it's probably the version of PAIN that you
|
||||
should try first.
|
||||
|
||||
Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com> for any help or question about this module.
|
||||
This module uses the framework provided by the banking addons,
|
||||
cf https://www.github.com/OCA/banking-addons
|
||||
|
||||
Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com>
|
||||
for any help or question about this module.
|
||||
''',
|
||||
'active': False,
|
||||
'installable': True,
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
'depends': ['account_direct_debit', 'account_banking_pain_base'],
|
||||
'external_dependencies': {
|
||||
'python': ['unidecode', 'lxml'],
|
||||
},
|
||||
},
|
||||
'data': [
|
||||
'security/original_mandate_required_security.xml',
|
||||
'account_banking_sdd_view.xml',
|
||||
@@ -49,13 +49,23 @@
|
||||
'description': '''
|
||||
Module to export direct debit payment orders in SEPA XML file format.
|
||||
|
||||
SEPA PAIN (PAyment INitiation) is the new european standard for Customer-to-Bank payment instructions. This module implements SEPA Direct Debit (SDD), more specifically PAIN versions 008.001.02, 008.001.03 and 008.001.04. It is part of the ISO 20022 standard, available on http://www.iso20022.org.
|
||||
SEPA PAIN (PAyment INitiation) is the new european standard for
|
||||
Customer-to-Bank payment instructions.
|
||||
|
||||
The Implementation Guidelines for SEPA Direct Debit published by the European Payments Council (http://http://www.europeanpaymentscouncil.eu) use PAIN version 008.001.02. So if you don't know which version your bank supports, you should try version 008.001.02 first.
|
||||
This module implements SEPA Direct Debit (SDD), more specifically PAIN
|
||||
versions 008.001.02, 008.001.03 and 008.001.04.
|
||||
It is part of the ISO 20022 standard, available on http://www.iso20022.org.
|
||||
|
||||
This module uses the framework provided by the banking addons, cf https://launchpad.net/banking-addons
|
||||
The Implementation Guidelines for SEPA Direct Debit published by the European
|
||||
Payments Council (http://http://www.europeanpaymentscouncil.eu) use PAIN
|
||||
version 008.001.02. So if you don't know which version your bank supports,
|
||||
you should try version 008.001.02 first.
|
||||
|
||||
Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com> for any help or question about this module.
|
||||
This module uses the framework provided by the banking addons,
|
||||
cf https://www.github.com/OCA/banking-addons
|
||||
|
||||
Please contact Alexis de Lattre from Akretion <alexis.delattre@akretion.com>
|
||||
for any help or question about this module.
|
||||
''',
|
||||
'active': False,
|
||||
'installable': True,
|
||||
|
||||
@@ -2,4 +2,4 @@ import test_payment_roundtrip
|
||||
|
||||
fast_suite = [
|
||||
test_payment_roundtrip,
|
||||
]
|
||||
]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (C) 2013 Therp BV (<http://therp.nl>)
|
||||
#
|
||||
#
|
||||
# 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
|
||||
@@ -83,7 +83,7 @@ class TestPaymentRoundtrip(SingleTransactionCase):
|
||||
reg('res.users').write(
|
||||
cr, uid, [uid], {
|
||||
'company_id': self.company_id})
|
||||
|
||||
|
||||
def setup_chart(self, reg, cr, uid):
|
||||
"""
|
||||
Set up the configurable chart of accounts and create periods
|
||||
@@ -115,7 +115,7 @@ class TestPaymentRoundtrip(SingleTransactionCase):
|
||||
reg('account.fiscalyear').create_period(
|
||||
cr, uid, [fiscalyear_id])
|
||||
|
||||
def setup_payables(self, reg, cr, uid):
|
||||
def setup_payables(self, reg, cr, uid, context=None):
|
||||
"""
|
||||
Set up suppliers and invoice them. Check that the invoices
|
||||
can be validated properly.
|
||||
@@ -126,25 +126,29 @@ class TestPaymentRoundtrip(SingleTransactionCase):
|
||||
'name': 'Supplier 1',
|
||||
'supplier': True,
|
||||
'country_id': self.country_id,
|
||||
'bank_ids': [(0, False, {
|
||||
'state': 'iban',
|
||||
'acc_number': 'NL42INGB0000454000',
|
||||
'bank': self.bank_id,
|
||||
'bank_bic': 'INGBNL2A',
|
||||
})],
|
||||
})
|
||||
'bank_ids': [
|
||||
(0, False, {
|
||||
'state': 'iban',
|
||||
'acc_number': 'NL42INGB0000454000',
|
||||
'bank': self.bank_id,
|
||||
'bank_bic': 'INGBNL2A',
|
||||
})
|
||||
],
|
||||
}, context=context)
|
||||
supplier2 = partner_model.create(
|
||||
cr, uid, {
|
||||
'name': 'Supplier 2',
|
||||
'supplier': True,
|
||||
'country_id': self.country_id,
|
||||
'bank_ids': [(0, False, {
|
||||
'state': 'iban',
|
||||
'acc_number': 'NL86INGB0002445588',
|
||||
'bank': self.bank_id,
|
||||
'bank_bic': 'INGBNL2A',
|
||||
})],
|
||||
})
|
||||
'bank_ids': [
|
||||
(0, False, {
|
||||
'state': 'iban',
|
||||
'acc_number': 'NL86INGB0002445588',
|
||||
'bank': self.bank_id,
|
||||
'bank_bic': 'INGBNL2A',
|
||||
})
|
||||
],
|
||||
}, context=context)
|
||||
self.payable_id = reg('account.account').search(
|
||||
cr, uid, [
|
||||
('company_id', '=', self.company_id),
|
||||
@@ -158,26 +162,29 @@ class TestPaymentRoundtrip(SingleTransactionCase):
|
||||
'type': 'in_invoice',
|
||||
'partner_id': supplier1,
|
||||
'account_id': self.payable_id,
|
||||
'invoice_line': [(0, False, {
|
||||
'name': 'Purchase 1',
|
||||
'price_unit': 100.0,
|
||||
'quantity': 1,
|
||||
'account_id': expense_id,})],
|
||||
'invoice_line': [
|
||||
(0, False, {
|
||||
'name': 'Purchase 1',
|
||||
'price_unit': 100.0,
|
||||
'quantity': 1,
|
||||
'account_id': expense_id,
|
||||
})
|
||||
],
|
||||
'reference_type': 'none',
|
||||
'supplier_invoice_number': 'INV1',
|
||||
}
|
||||
}
|
||||
self.invoice_ids = [
|
||||
invoice_model.create(
|
||||
cr, uid, values, context={
|
||||
'type': 'in_invoice',
|
||||
})]
|
||||
values.update({
|
||||
'partner_id': supplier2,
|
||||
'name': 'Purchase 2',
|
||||
'reference_type': 'structured',
|
||||
'supplier_invoice_number': 'INV2',
|
||||
'reference': 'STR2',
|
||||
})
|
||||
'partner_id': supplier2,
|
||||
'name': 'Purchase 2',
|
||||
'reference_type': 'structured',
|
||||
'supplier_invoice_number': 'INV2',
|
||||
'reference': 'STR2',
|
||||
})
|
||||
self.invoice_ids.append(
|
||||
invoice_model.create(
|
||||
cr, uid, values, context={
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (C) 2013 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
# All Rights Reserved
|
||||
@@ -22,6 +22,7 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
|
||||
def migrate(cr, version):
|
||||
if not version:
|
||||
return
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (C) 2013 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
# All Rights Reserved
|
||||
@@ -25,27 +25,29 @@
|
||||
import logging
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
def rename_columns(cr, column_spec):
|
||||
"""
|
||||
Rename table columns. Taken from OpenUpgrade.
|
||||
|
||||
:param column_spec: a hash with table keys, with lists of tuples as values. \
|
||||
Tuples consist of (old_name, new_name).
|
||||
:param column_spec: a hash with table keys, with lists of tuples as \
|
||||
values. Tuples consist of (old_name, new_name).
|
||||
|
||||
"""
|
||||
for table in column_spec.keys():
|
||||
for (old, new) in column_spec[table]:
|
||||
logger.info("table %s, column %s: renaming to %s",
|
||||
table, old, new)
|
||||
cr.execute('ALTER TABLE "%s" RENAME "%s" TO "%s"' % (table, old, new,))
|
||||
logger.info("table %s, column %s: renaming to %s", table, old, new)
|
||||
cr.execute('ALTER TABLE %s RENAME %s TO %s', (table, old, new,))
|
||||
cr.execute('DROP INDEX IF EXISTS "%s_%s_index"' % (table, old))
|
||||
|
||||
|
||||
def migrate(cr, version):
|
||||
if not version:
|
||||
return
|
||||
|
||||
# rename field debit_move_line_id
|
||||
rename_columns(cr, {
|
||||
'payment_line': [
|
||||
('debit_move_line_id', 'banking_addons_61_debit_move_line_id'),
|
||||
]})
|
||||
'payment_line': [
|
||||
('debit_move_line_id', 'banking_addons_61_debit_move_line_id'),
|
||||
]
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (C) 2011 - 2013 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
# All Rights Reserved
|
||||
@@ -33,13 +33,13 @@ credited afterwards. Such a creditation is called a storno.
|
||||
|
||||
Invoice workflow:
|
||||
|
||||
1 the sale leads to
|
||||
1 the sale leads to
|
||||
1300 Debtors 100
|
||||
8000 Sales 100
|
||||
|
||||
Balance:
|
||||
Debtors 2000 |
|
||||
Sales | 2000
|
||||
Balance:
|
||||
Debtors 2000 |
|
||||
Sales | 2000
|
||||
|
||||
2 an external booking takes place
|
||||
1100 Bank 100
|
||||
@@ -59,11 +59,11 @@ This module implements the following diversion:
|
||||
|
||||
2000 Transfer account 100 |
|
||||
1300 Debtors | 100
|
||||
Reconciliation takes place between 1 and 2a.
|
||||
Reconciliation takes place between 1 and 2a.
|
||||
The invoice gets set to state 'paid', and 'reconciled' = True
|
||||
|
||||
Balance:
|
||||
Debtors 0 |
|
||||
Debtors 0 |
|
||||
Transfer account 2000 |
|
||||
Bank 0 |
|
||||
Sales | 2000
|
||||
@@ -76,7 +76,7 @@ Balance:
|
||||
Reconciliation takes place between 3a and 2a
|
||||
|
||||
Balance:
|
||||
Debtors 0 |
|
||||
Debtors 0 |
|
||||
Transfer account 0 |
|
||||
Bank 2000 |
|
||||
Sales | 2000
|
||||
@@ -84,55 +84,57 @@ Balance:
|
||||
4 a storno from invoice [1] triggers a new booking on the bank account
|
||||
1300 Debtors 100 |
|
||||
1100 Bank | 100
|
||||
|
||||
|
||||
Balance:
|
||||
Debtors 100 |
|
||||
Debtors 100 |
|
||||
Transfer account 0 |
|
||||
Bank 1900 |
|
||||
Sales | 2000
|
||||
|
||||
The reconciliation of 2a is undone. The booking of 2a is reconciled
|
||||
The reconciliation of 2a is undone. The booking of 2a is reconciled
|
||||
with the booking of 4 instead.
|
||||
The payment line attribute 'storno' is set to True and the invoice
|
||||
state is no longer 'paid'.
|
||||
|
||||
Two cases need to be distinguisted:
|
||||
1) If the storno is a manual storno from the partner, the invoice is set to
|
||||
state 'debit_denied', with 'reconciled' = False
|
||||
state 'debit_denied', with 'reconciled' = False
|
||||
This module implements this option by allowing the bank module to call
|
||||
|
||||
|
||||
netsvc.LocalService("workflow").trg_validate(
|
||||
uid, 'account.invoice', ids, 'debit_denied', cr)
|
||||
|
||||
2) If the storno is an error generated by the bank (assumingly non-fatal),
|
||||
the invoice is reopened for the next debit run. This is a call to existing
|
||||
|
||||
the invoice is reopened for the next debit run.
|
||||
This is a call to existing
|
||||
|
||||
netsvc.LocalService("workflow").trg_validate(
|
||||
uid, 'account.invoice', ids, 'open_test', cr)
|
||||
|
||||
Should also be adding a log entry on the invoice for tracing purposes
|
||||
|
||||
self._log_event(cr, uid, ids, -1.0, 'Debit denied')
|
||||
self._log_event(cr, uid, ids, -1.0, 'Debit denied')
|
||||
|
||||
If not for that funny comment
|
||||
"#TODO: implement messages system" in account/invoice.py
|
||||
|
||||
Repeating non-fatal fatal errors need to be dealt with manually by checking
|
||||
open invoices with a matured invoice- or due date.
|
||||
"""
|
||||
"""
|
||||
|
||||
|
||||
class account_invoice(orm.Model):
|
||||
_inherit = "account.invoice"
|
||||
|
||||
def __init__(self, pool, cr):
|
||||
"""
|
||||
"""
|
||||
Adding a state to the hardcoded state list of the inherited
|
||||
model. The alternative is duplicating the field definition
|
||||
model. The alternative is duplicating the field definition
|
||||
in columns but only one module can do that!
|
||||
|
||||
Maybe apply a similar trick when overriding the buttons' 'states' attributes
|
||||
in the form view, manipulating the xml in fields_view_get().
|
||||
"""
|
||||
Maybe apply a similar trick when overriding the buttons' 'states'
|
||||
attributes in the form view, manipulating the xml in fields_view_get().
|
||||
"""
|
||||
super(account_invoice, self).__init__(pool, cr)
|
||||
invoice_obj = pool.get('account.invoice')
|
||||
invoice_obj._columns['state'].selection.append(
|
||||
@@ -144,8 +146,8 @@ class account_invoice(orm.Model):
|
||||
number = self.read(
|
||||
cr, uid, invoice_id, ['number'], context=context)['number']
|
||||
raise orm.except_orm(
|
||||
_('Error !'),
|
||||
_('You cannot set invoice \'%s\' to state \'debit denied\', ' +
|
||||
_('Error !'),
|
||||
_("You cannot set invoice '%s' to state 'debit denied', "
|
||||
'as it is still reconciled.') % number)
|
||||
self.write(cr, uid, ids, {'state': 'debit_denied'}, context=context)
|
||||
for inv_id, name in self.name_get(cr, uid, ids, context=context):
|
||||
@@ -154,9 +156,9 @@ class account_invoice(orm.Model):
|
||||
return True
|
||||
|
||||
def test_undo_debit_denied(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
"""
|
||||
Called from the workflow. Used to unset paid state on
|
||||
invoices that were paid with bank transfers which are being cancelled
|
||||
invoices that were paid with bank transfers which are being cancelled
|
||||
"""
|
||||
for invoice in self.read(cr, uid, ids, ['reconciled'], context):
|
||||
if not invoice['reconciled']:
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
from operator import itemgetter
|
||||
from openerp.osv import fields, orm
|
||||
|
||||
|
||||
class account_move_line(orm.Model):
|
||||
_inherit = "account.move.line"
|
||||
|
||||
@@ -48,7 +49,7 @@ class account_move_line(orm.Model):
|
||||
AND pl.storno is false
|
||||
AND po.state != 'cancel') AS amount
|
||||
FROM account_move_line ml
|
||||
WHERE id IN %s""", (tuple(ids),))
|
||||
WHERE id IN %s""", (tuple(ids), ))
|
||||
r = dict(cr.fetchall())
|
||||
return r
|
||||
|
||||
@@ -80,12 +81,12 @@ class account_move_line(orm.Model):
|
||||
WHERE type=%s AND active)
|
||||
AND reconcile_id IS null
|
||||
AND debit > 0
|
||||
AND ''' + where + ' and ' + query), ('receivable',)+sql_args )
|
||||
AND ''' + where + ' and ' + query), ('receivable', ) + sql_args)
|
||||
|
||||
res = cr.fetchall()
|
||||
if not res:
|
||||
return [('id', '=', '0')]
|
||||
return [('id', 'in', map(lambda x:x[0], res))]
|
||||
return [('id', 'in', map(lambda x: x[0], res))]
|
||||
|
||||
def line2bank(self, cr, uid, ids, payment_mode_id, context=None):
|
||||
'''I have to inherit this function for direct debits to fix the
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from openerp.osv import orm, fields
|
||||
import netsvc
|
||||
from tools.translate import _
|
||||
from openerp.osv import orm
|
||||
|
||||
|
||||
class payment_order(orm.Model):
|
||||
_inherit = 'payment.order'
|
||||
|
||||
def test_undo_done(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
"""
|
||||
Called from the workflow. Used to unset done state on
|
||||
payment orders that were reconciled with bank transfers
|
||||
which are being cancelled
|
||||
which are being cancelled
|
||||
"""
|
||||
for order in self.browse(cr, uid, ids, context=context):
|
||||
if order.payment_order_type == 'debit':
|
||||
|
||||
@@ -3,6 +3,7 @@ from openerp.osv import orm, fields
|
||||
import netsvc
|
||||
from tools.translate import _
|
||||
|
||||
|
||||
class payment_line(orm.Model):
|
||||
_inherit = 'payment.line'
|
||||
|
||||
@@ -22,15 +23,15 @@ class payment_line(orm.Model):
|
||||
:param payment_line_id: the single payment line id
|
||||
:param amount: the (signed) amount debited from the bank account
|
||||
:param currency: the bank account's currency *browse object*
|
||||
:param boolean storno_retry: when True, attempt to reopen the invoice, \
|
||||
set the invoice to 'Debit denied' otherwise.
|
||||
:param boolean storno_retry: when True, attempt to reopen the \
|
||||
invoice, set the invoice to 'Debit denied' otherwise.
|
||||
:return: an incomplete reconcile for the caller to fill
|
||||
:rtype: database id of an account.move.reconcile resource.
|
||||
"""
|
||||
|
||||
move_line_obj = self.pool.get('account.move.line')
|
||||
reconcile_obj = self.pool.get('account.move.reconcile')
|
||||
line = self.browse(cr, uid, payment_line_id)
|
||||
transit_move_line = line.transit_move_line_id
|
||||
reconcile_id = False
|
||||
if (line.transit_move_line_id and not line.storno and
|
||||
self.pool.get('res.currency').is_zero(
|
||||
@@ -43,14 +44,13 @@ class payment_line(orm.Model):
|
||||
# Actually, given the nature of a direct debit order and storno,
|
||||
# we should not need to take partial into account on the side of
|
||||
# the transit_move_line.
|
||||
if line.transit_move_line_id.reconcile_partial_id:
|
||||
reconcile_id = line.transit_move_line_id.reconcile_partial_id.id
|
||||
attribute = 'reconcile_partial_id'
|
||||
if len(line.transit_move_line_id.reconcile_id.line_partial_ids) == 2:
|
||||
if transit_move_line.reconcile_partial_id:
|
||||
reconcile_id = transit_move_line.reconcile_partial_id.id
|
||||
if len(transit_move_line.reconcile_id.line_partial_ids) == 2:
|
||||
# reuse the simple reconcile for the storno transfer
|
||||
reconcile_obj.write(
|
||||
cr, uid, reconcile_id, {
|
||||
'line_id': [(6, 0, line.transit_move_line_id.id)],
|
||||
'line_id': [(6, 0, transit_move_line.id)],
|
||||
'line_partial_ids': [(6, 0, [])],
|
||||
}, context=context)
|
||||
else:
|
||||
@@ -58,27 +58,27 @@ class payment_line(orm.Model):
|
||||
# and a new one for reconciling the storno transfer
|
||||
reconcile_obj.write(
|
||||
cr, uid, reconcile_id, {
|
||||
'line_partial_ids': [(3, line.transit_move_line_id.id)],
|
||||
'line_partial_ids': [(3, transit_move_line.id)],
|
||||
}, context=context)
|
||||
reconcile_id = reconcile_obj.create(
|
||||
cr, uid, {
|
||||
'type': 'auto',
|
||||
'line_id': [(6, 0, line.transit_move_line_id.id)],
|
||||
'line_id': [(6, 0, transit_move_line.id)],
|
||||
}, context=context)
|
||||
elif line.transit_move_line_id.reconcile_id:
|
||||
reconcile_id = line.transit_move_line_id.reconcile_id.id
|
||||
if len(line.transit_move_line_id.reconcile_id.line_id) == 2:
|
||||
elif transit_move_line.reconcile_id:
|
||||
reconcile_id = transit_move_line.reconcile_id.id
|
||||
if len(transit_move_line.reconcile_id.line_id) == 2:
|
||||
# reuse the simple reconcile for the storno transfer
|
||||
reconcile_obj.write(
|
||||
cr, uid, reconcile_id, {
|
||||
'line_id': [(6, 0, [line.transit_move_line_id.id])]
|
||||
'line_id': [(6, 0, [transit_move_line.id])]
|
||||
}, context=context)
|
||||
else:
|
||||
# split up the original reconcile in a partial one
|
||||
# and a new one for reconciling the storno transfer
|
||||
partial_ids = [
|
||||
x.id for x in line.transit_move_line_id.reconcile_id.line_id
|
||||
if x.id != line.transit_move_line_id.id
|
||||
partial_ids = [
|
||||
x.id for x in transit_move_line.reconcile_id.line_id
|
||||
if x.id != transit_move_line.id
|
||||
]
|
||||
reconcile_obj.write(
|
||||
cr, uid, reconcile_id, {
|
||||
@@ -88,7 +88,7 @@ class payment_line(orm.Model):
|
||||
reconcile_id = reconcile_obj.create(
|
||||
cr, uid, {
|
||||
'type': 'auto',
|
||||
'line_id': [(6, 0, line.transit_move_line_id.id)],
|
||||
'line_id': [(6, 0, transit_move_line.id)],
|
||||
}, context=context)
|
||||
# mark the payment line for storno processed
|
||||
if reconcile_id:
|
||||
@@ -104,7 +104,7 @@ class payment_line(orm.Model):
|
||||
return reconcile_id
|
||||
|
||||
def get_storno_account_id(self, cr, uid, payment_line_id, amount,
|
||||
currency, context=None):
|
||||
currency, context=None):
|
||||
"""
|
||||
Check the match of the arguments, and return the account associated
|
||||
with the storno.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (C) 2013 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
# All Rights Reserved
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (C) 2012 - 2013 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
# All Rights Reserved
|
||||
@@ -39,7 +39,7 @@
|
||||
This module is compatible with OpenERP 7.0.
|
||||
|
||||
The IBAN module in OpenERP 6.1/7.0 registers the IBAN
|
||||
on the same field as the domestic account number,
|
||||
on the same field as the domestic account number,
|
||||
instead of keeping both on separate fields as is the
|
||||
case in 6.0.
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -27,7 +27,8 @@
|
||||
'category': 'Banking addons',
|
||||
'depends': ['account_payment'],
|
||||
'description': '''
|
||||
When composing a payment order, select all candidates by default (in the second step of the "Select invoices to pay" wizard).
|
||||
When composing a payment order, select all candidates by default
|
||||
(in the second step of the "Select invoices to pay" wizard).
|
||||
''',
|
||||
'installable': True,
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (C) 2011 - 2013 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
# All Rights Reserved
|
||||
@@ -24,11 +24,12 @@
|
||||
|
||||
from openerp.osv import orm
|
||||
|
||||
|
||||
class payment_order_create(orm.TransientModel):
|
||||
_inherit = 'payment.order.create'
|
||||
|
||||
def default_get(self, cr, uid, fields_list, context=None):
|
||||
"""
|
||||
"""
|
||||
Automatically add the candidate move lines to
|
||||
the payment order, instead of only applying them
|
||||
to the domain.
|
||||
@@ -39,13 +40,15 @@ class payment_order_create(orm.TransientModel):
|
||||
been placed in the context at object
|
||||
creation time.
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
res = super(payment_order_create, self).default_get(
|
||||
cr, uid, fields_list, context=context)
|
||||
|
||||
if (fields_list and 'entries' in fields_list
|
||||
and 'entries' not in res
|
||||
and context and context.get('line_ids', False)
|
||||
):
|
||||
if (fields_list
|
||||
and 'entries' in fields_list
|
||||
and 'entries' not in res
|
||||
and context.get('line_ids', False)):
|
||||
res['entries'] = context['line_ids']
|
||||
|
||||
return res
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user