First import

This commit is contained in:
Pieter J. Kersten
2010-01-26 21:42:56 +01:00
commit 09f126ad74
18 changed files with 4670 additions and 0 deletions

34
__init__.py Normal file
View File

@@ -0,0 +1,34 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
# All Rights Reserved
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# 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 General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
print 'Importing account_banking.account_banking'
import account_banking
print 'Importing account_banking.parsers'
import parsers
print 'Importing account_banking.wizard'
import wizard
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

104
__terp__.py Normal file
View File

@@ -0,0 +1,104 @@
##############################################################################
#
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
# All Rights Reserved
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# 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 General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'Account Banking',
'version': '0.1',
'license': 'GPL-3',
'author': 'EduSense BV',
'website': 'http://www.edusense.nl',
'category': 'Account Banking',
'depends': ['base', 'base_iban', 'account', 'account_payment'],
'init_xml': [],
'update_xml': [
#'security/ir.model.access.csv',
'account_banking_import_wizard.xml',
'account_banking_view.xml',
'account_banking_workflow.xml',
],
'demo_xml': [],
'description': '''
Module to do banking.
Note: This module is depending on BeautifulSoup when using the Dutch
online database. Make sure it is installed.
This modules tries to combine all current banking import and export
schemes. Rationale for this is that it is quite common to have foreign
bank account numbers next to national bank account numbers. The current
approach, which hides the national banking interface schemes in the
l10n_xxx modules, makes it very difficult to use these simultanious.
A more banking oriented approach seems more logical and cleaner.
Changes to default OpenERP:
* Puts focus on the real life messaging with banks:
+ Bank statement lines upgraded to independent bank transactions.
+ Banking statements have no special accountancy meaning, they're just
message envelopes for a number of bank transactions.
+ Bank statements can be either encoded by hand to reflect the document
version of Bank Statements, or created as an optional side effect of
importing Bank Transactions.
* Preparations for SEPA:
+ 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
+ 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.
* Adds dropin extensible import facility for bank communication in:
- Drop-in input parser development.
- MultiBank (NL) format transaction files available as
account_banking_nl_multibank,
- (todo) MT940 (Swift) format transaction files,
- (todo) CODA (BE) format transaction files,
- (wish) SEPA Credits (ISO 200022) messages,
* Extends payments for digital banking:
+ Adapted workflow in payments to reflect banking operations
+ Relies on account_payment mechanics to extend with export generators.
- ClieOp3 (NL) payment and direct debit orders files available as
account_banking_nl_clieop
- (wish) BTL91 (NL) payment orders files (no format description available),
- (wish) SEPA Direct Debits (ISO 200022) messages
* Additional features for the import/export mechanism:
+ Automatic matching and creation of bank accounts, banks and partners,
during import of statements.
+ Automatic matching with invoices and payments.
+ Sound import mechanism, allowing multiple imports of the same
transactions repeated over multiple files.
+ Journal configuration per bank account.
+ Business logic and format parsing strictly separated to ease the
development of new parsers.
+ No special configuration needed for the parsers, new parsers are
recognized and made available at server (re)start.
''',
'active': False,
'installable': True,
}

753
account_banking.py Normal file
View File

@@ -0,0 +1,753 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
# All Rights Reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
'''
This module shows resemblance to both account_bankimport/bankimport.py,
account/account_bank_statement.py and account_payment(_export). All hail to
the makers. account_bankimport is only referenced for their ideas and the
framework of the filters, which they in their turn seem to have derived
from account_coda.
Modifications are extensive:
1. In relation to account/account_bank_statement.py:
account.bank.statement is effectively stripped from its account.period
association, while account.bank.statement.line is extended with the same
association, thereby reflecting real world usage of bank.statement as a
list of bank transactions and bank.statement.line as a bank transaction.
2. In relation to account/account_bankimport:
All filter objects and extensions to res.company are removed. Instead a
flexible auto-loading and auto-browsing plugin structure is created,
whereby business logic and encoding logic are strictly separated.
Both parsers and business logic are rewritten from scratch.
The association of account.journal with res.company is replaced by an
association of account.journal with res.partner.bank, thereby allowing
multiple bank accounts per company and one journal per bank account.
The imported bank statement file does not result in a single 'bank
statement', but in a list of bank statements by definition of whatever the
bank sees as a statement. Every imported bank statement contains at least
one bank transaction, which is a modded account.bank.statement.line.
3. In relation to account_payment:
An additional state was inserted between 'open' and 'done', to reflect a
exported bank orders file which was not reported back through statements.
The import of statements matches the payments and reconciles them when
needed, flagging them 'done'. When no export wizards are found, the
default behavior is to flag the orders as 'sent', not as 'done'.
'''
import time
from osv import osv, fields
from tools.translate import _
class account_banking_account_settings(osv.osv):
'''Default Journal for Bank Account'''
_name = 'account.banking.account.settings'
_description = __doc__
_columns = {
'company_id': fields.many2one('res.company', 'Company', select=True,
required=True),
'partner_bank_id': fields.many2one('res.partner.bank', 'Bank Account',
select=True, required=True),
'journal_id': fields.many2one('account.journal', 'Journal',
required=True),
'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 '
'by a customer, or when no matching payment can be found. '
' Mind that you can correct movements before confirming them.'
),
required=True
),
'default_debit_account_id': fields.many2one(
'account.account', 'Default debit account',
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.'
),
),
}
def _default_company(self, cursor, uid, context={}):
user = self.pool.get('res.users').browse(cursor, uid, uid, context=context)
if user.company_id:
return user.company_id.id
return self.pool.get('res.company').search(cursor, uid,
[('parent_id', '=', False)]
)[0]
_defaults = {
'company_id': _default_company,
}
account_banking_account_settings()
class account_banking_imported_file(osv.osv):
'''Imported Bank Statements File'''
_name = 'account.banking.imported.file'
_description = __doc__
_columns = {
'company_id': fields.many2one('res.company', 'Company',
select=True, readonly=True
),
'date': fields.datetime('Import Date', readonly=False, select=True),
'format': fields.char('File Format', size=20, readonly=False),
'file': fields.binary('Raw Data', readonly=False),
'log': fields.text('Import Log', readonly=False),
'user_id': fields.many2one('res.users', 'Responsible User',
readonly=False, select=True
),
'state': fields.selection(
[('unfinished', 'Unfinished'),
('error', 'Error'),
('ready', 'Finished'),
], 'State', select=True, readonly=True
),
'statement_ids': fields.one2many('account.bank.statement',
'banking_id', 'Statements',
readonly=False,
),
}
_defaults = {
'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
'user_id': lambda self, cursor, uid, context: uid,
}
account_banking_imported_file()
class account_bank_statement(osv.osv):
'''
Extensions from account_bank_statement:
1. Removed period_id (transformed to optional boolean) - as it is no
longer needed.
2. Extended 'button_confirm' trigger to cope with the period per
statement_line situation.
3. Added optional relation with imported statements file
'''
_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,
),
}
_defaults = {
'period_id': lambda *a: False,
}
def _get_period(self, cursor, uid, date, context={}):
'''
Find matching period for date, not meant for _defaults.
'''
period_obj = self.pool.get('account.period')
periods = period_obj.find(cursor, uid, dt=date, context=context)
return periods and periods[0] or False
def button_confirm(self, cursor, uid, ids, context=None):
# This is largely a copy of the original code in account
# As there is no valid inheritance mechanism for large actions, this
# is the only option to add functionality to existing actions.
# WARNING: when the original code changes, this trigger has to be
# updated in sync.
done = []
res_currency_obj = self.pool.get('res.currency')
res_users_obj = self.pool.get('res.users')
account_move_obj = self.pool.get('account.move')
account_move_line_obj = self.pool.get('account.move.line')
account_bank_statement_line_obj = \
self.pool.get('account.bank.statement.line')
company_currency_id = res_users_obj.browse(cursor, uid, uid,
context=context).company_id.currency_id.id
for st in self.browse(cursor, uid, ids, context):
if not st.state=='draft':
continue
end_bal = st.balance_end or 0.0
if not (abs(end_bal - st.balance_end_real) < 0.0001):
raise osv.except_osv(_('Error !'),
_('The statement balance is incorrect !\n') +
_('The expected balance (%.2f) is different '
'than the computed one. (%.2f)') % (
st.balance_end_real, st.balance_end
))
if (not st.journal_id.default_credit_account_id) \
or (not st.journal_id.default_debit_account_id):
raise osv.except_osv(_('Configration Error !'),
_('Please verify that an account is defined in the journal.'))
for line in st.move_line_ids:
if line.state != 'valid':
raise osv.except_osv(_('Error !'),
_('The account entries lines are not in valid state.'))
for move in st.line_ids:
period_id = self._get_period(cursor, uid, move.date, context=context)
move_id = account_move_obj.create(cursor, uid, {
'journal_id': st.journal_id.id,
'period_id': period_id,
}, context=context)
account_bank_statement_line_obj.write(cursor, uid, [move.id], {
'move_ids': [(4, move_id, False)]
})
if not move.amount:
continue
torec = []
if move.amount >= 0:
account_id = st.journal_id.default_credit_account_id.id
else:
account_id = st.journal_id.default_debit_account_id.id
acc_cur = ((move.amount<=0) and st.journal_id.default_debit_account_id) \
or move.account_id
amount = res_currency_obj.compute(cursor, uid, st.currency.id,
company_currency_id, move.amount, context=context,
account=acc_cur)
if move.reconcile_id and move.reconcile_id.line_new_ids:
for newline in move.reconcile_id.line_new_ids:
amount += newline.amount
val = {
'name': move.name,
'date': move.date,
'ref': move.ref,
'move_id': move_id,
'partner_id': ((move.partner_id) and move.partner_id.id) or False,
'account_id': (move.account_id) and move.account_id.id,
'credit': ((amount>0) and amount) or 0.0,
'debit': ((amount<0) and -amount) or 0.0,
'statement_id': st.id,
'journal_id': st.journal_id.id,
'period_id': period_id,
'currency_id': st.currency.id,
}
amount = res_currency_obj.compute(cursor, uid, st.currency.id,
company_currency_id, move.amount, context=context,
account=acc_cur)
if move.account_id and move.account_id.currency_id:
val['currency_id'] = move.account_id.currency_id.id
if company_currency_id==move.account_id.currency_id.id:
amount_cur = move.amount
else:
amount_cur = res_currency_obj.compute(cursor, uid, company_currency_id,
move.account_id.currency_id.id, amount, context=context,
account=acc_cur)
val['amount_currency'] = amount_cur
torec.append(account_move_line_obj.create(cursor, uid, val , context=context))
if move.reconcile_id and move.reconcile_id.line_new_ids:
for newline in move.reconcile_id.line_new_ids:
account_move_line_obj.create(cursor, uid, {
'name': newline.name or move.name,
'date': move.date,
'ref': move.ref,
'move_id': move_id,
'partner_id': ((move.partner_id) and move.partner_id.id) or False,
'account_id': (newline.account_id) and newline.account_id.id,
'debit': newline.amount>0 and newline.amount or 0.0,
'credit': newline.amount<0 and -newline.amount or 0.0,
'statement_id': st.id,
'journal_id': st.journal_id.id,
'period_id': period_id,
}, context=context)
# Fill the secondary amount/currency
# if currency is not the same than the company
amount_currency = False
currency_id = False
if st.currency.id <> company_currency_id:
amount_currency = move.amount
currency_id = st.currency.id
account_move_line_obj.create(cursor, uid, {
'name': move.name,
'date': move.date,
'ref': move.ref,
'move_id': move_id,
'partner_id': ((move.partner_id) and move.partner_id.id) or False,
'account_id': account_id,
'credit': ((amount < 0) and -amount) or 0.0,
'debit': ((amount > 0) and amount) or 0.0,
'statement_id': st.id,
'journal_id': st.journal_id.id,
'period_id': period_id,
'amount_currency': amount_currency,
'currency_id': currency_id,
}, context=context)
for line in account_move_line_obj.browse(cursor, uid, [x.id for x in
account_move_obj.browse(cursor, uid, move_id, context=context).line_id
], context=context):
if line.state != 'valid':
raise osv.except_osv(
_('Error !'),
_('Account move line "%s" is not valid')
% line.name
)
if move.reconcile_id and move.reconcile_id.line_ids:
torec += map(lambda x: x.id, move.reconcile_id.line_ids)
#try:
if abs(move.reconcile_amount-move.amount)<0.0001:
account_move_line_obj.reconcile(
cursor, uid, torec, 'statement', context
)
else:
account_move_line_obj.reconcile_partial(
cursor, uid, torec, 'statement', context
)
#except:
# raise osv.except_osv(
# _('Error !'),
# _('Unable to reconcile entry "%s": %.2f') %
# (move.name, move.amount)
# )
if st.journal_id.entry_posted:
account_move_obj.write(cursor, uid, [move_id], {'state':'posted'})
done.append(st.id)
self.write(cursor, uid, done, {'state':'confirm'}, context=context)
return True
account_bank_statement()
class account_bank_statement_line(osv.osv):
'''
Extension on basic class:
1. Extra links to account.period and res.partner.bank for tracing and
matching.
2. Extra 'trans' field to carry the transaction id of the bank.
3. Extra 'international' flag to indicate the missing of a remote
account number. Some banks use seperate international banking
modules that do not integrate with the standard transaction files.
4. Readonly states for most fields except when in draft.
'''
_inherit = 'account.bank.statement.line'
_description = 'Bank Transaction'
def _get_period(self, cursor, uid, context={}):
date = context.get('date') and context['date'] or None
periods = self.pool.get('account.period').find(cursor, uid, dt=date)
return periods and periods[0] or False
def _seems_international(self, cursor, uid, context={}):
'''
Some banks have seperate international banking modules which do not
translate correctly into the national formats. Instead, they
leave key fields blank and signal this anomaly with a special
transfer type.
With the introduction of SEPA, this may worsen greatly, as SEPA
payments are considered to be analogous to international payments
by most local formats.
'''
# Quick and dirty check: if remote bank account is missing, assume
# international transfer
return not (
context.get('partner_bank_id') and context['partner_bank_id']
)
# Not so dirty check: check if partner_id is set. If it is, check the
# default/invoice addresses country. If it is the same as our
# company's, its local, else international.
# TODO: to be done
_columns = {
# Redefines
'amount': fields.float('Amount', readonly=True,
states={'draft': [('readonly', False)]}),
'ref': fields.char('Ref.', size=32, readonly=True,
states={'draft': [('readonly', False)]}),
'name': fields.char('Name', size=64, required=True, 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={'confirm': [('readonly', True)]}),
# Not used yet, but usefull in the future.
'international': fields.boolean('International Transaction',
required=False,
states={'confirm': [('readonly', True)]},
),
}
_defaults = {
'period_id': _get_period,
'international': _seems_international,
}
def onchange_partner_id(self, cursor, uid, line_id, partner_id, type,
currency_id, context={}
):
if not partner_id:
return {}
users_obj = self.pool.get('res.users')
partner_obj = self.pool.get('res.partner')
company_currency_id = users_obj.browse(
cursor, uid, uid, context=context
).company_id.currency_id.id
if not currency_id:
currency_id = company_currency_id
partner = partner_obj.browse(cursor, uid, partner_id, context=context)
if partner.supplier and not part.customer:
account_id = part.property_account_payable.id
type = 'supplier'
elif partner.supplier and not part.customer:
account_id = part.property_account_receivable.id
type = 'customer'
else:
account_id = 0
type = 'general'
return {'value': {'type': type, 'account_id': account_id}}
def write(self, cursor, uid, ids, values, context={}):
# TODO: Not sure what to do with this, as it seems that most of
# this code is related to res_partner_bank and not to this class.
account_numbers = []
bank_obj = self.pool.get('res.partner.bank')
statement_line_obj = self.pool.get('account.bank.statement.line')
if 'partner_id' in values:
bank_account_ids = bank_obj.search(cursor, uid,
[('partner_id','=', values['partner_id'])]
)
bank_accounts = bank_obj.browse(cursor, uid, bank_account_ids)
import_account_numbers = statement_line_obj.browse(cursor, uid, ids)
for accno in bank_accounts:
# Allow acc_number and iban to co-exist (SEPA will unite the
# two, but - as seen now - in an uneven pace per country)
if accno.acc_number:
account_numbers.append(accno.acc_number)
if accno.iban:
account_numbers.append(accno.iban)
if any([x for x in import_account_numbers if x.bank_accnumber in
account_numbers]):
for accno in import_account_numbers:
account_data = _get_account_data(accno.bank_accnumber)
if account_data:
bank_id = bank_obj.search(cursor, uid, [
('name', '=', account_data['bank_name'])
])
if not bank_id:
bank_id = bank_obj.create(cursor, uid, {
'name': account_data['bank_name'],
'bic': account_data['bic'],
'active': 1,
})
else:
bank_id = bank_id[0]
bank_acc = bank_obj.create(cursor, uid, {
'state': 'bank',
'partner_id': values['partner_id'],
'bank': bank_id,
'acc_number': accno.bank_accnumber,
})
bank_iban = bank_obj.create(cursor, uid, {
'state': 'iban',
'partner_id': values['partner_id'],
'bank': bank_id,
'iban': account_data['iban'],
})
else:
bank_acc = bank_obj.create(cursor, uid, {
'state': 'bank',
'partner_id': values['partner_id'],
'acc_number': accno.bank_accnumber,
})
return super(account_bank_statement_line, self).write(
cursor, uid, ids, values, context
)
account_bank_statement_line()
class payment_type(osv.osv):
'''
Make description field translatable #, add country context
'''
_inherit = 'payment.type'
_columns = {
'name': fields.char('Name', size=64, required=True, translate=True,
help='Payment Type'
),
#'country_id': fields.many2one('res.country', 'Country',
# required=False,
# help='Use this to limit this type to a specific country'
# ),
}
#_defaults = {
# 'country_id': lambda *a: False,
#}
payment_type()
class payment_line(osv.osv):
'''
Add extra export_state and date_done fields; make destination bank account
mandatory, as it makes no sense to send payments into thin air.
'''
_inherit = 'payment.line'
_columns = {
# New fields
'bank_id': fields.many2one('res.partner.bank',
'Destination Bank account',
required=True
),
'export_state': fields.selection([
('draft', 'Draft'),
('open','Confirmed'),
('cancel','Cancelled'),
('sent', 'Sent'),
('done','Done'),
], 'State', select=True
),
# Redefined fields: added states
'date_done': fields.datetime('Date Confirmed', select=True,
readonly=True),
'name': fields.char(
'Your Reference', size=64, required=True,
states={
'sent': [('readonly', True)],
'done': [('readonly', True)]
},
),
'communication': fields.char(
'Communication', size=64, required=True,
help=("Used as the message between ordering customer and current "
"company. Depicts 'What do you want to say to the recipient"
" about this order ?'"
),
states={
'sent': [('readonly', True)],
'done': [('readonly', True)]
},
),
'communication2': fields.char(
'Communication 2', size=64,
help='The successor message of Communication.',
states={
'sent': [('readonly', True)],
'done': [('readonly', True)]
},
),
'move_line_id': fields.many2one(
'account.move.line', 'Entry line',
domain=[('reconcile_id','=', False),
('account_id.type', '=','payable')
],
help=('This Entry Line will be referred for the information of '
'the ordering customer.'
),
states={
'sent': [('readonly', True)],
'done': [('readonly', True)]
},
),
'amount_currency': fields.float(
'Amount in Partner Currency', digits=(16,2),
required=True,
help='Payment amount in the partner currency',
states={
'sent': [('readonly', True)],
'done': [('readonly', True)]
},
),
'currency': fields.many2one(
'res.currency', 'Partner Currency', required=True,
states={
'sent': [('readonly', True)],
'done': [('readonly', True)]
},
),
'bank_id': fields.many2one(
'res.partner.bank', 'Destination Bank account',
states={
'sent': [('readonly', True)],
'done': [('readonly', True)]
},
),
'order_id': fields.many2one(
'payment.order', 'Order', required=True,
ondelete='cascade', select=True,
states={
'sent': [('readonly', True)],
'done': [('readonly', True)]
},
),
'partner_id': fields.many2one(
'res.partner', string="Partner", required=True,
help='The Ordering Customer',
states={
'sent': [('readonly', True)],
'done': [('readonly', True)]
},
),
'date': fields.date(
'Payment Date',
help=("If no payment date is specified, the bank will treat this "
"payment line directly"
),
states={
'sent': [('readonly', True)],
'done': [('readonly', True)]
},
),
'state': fields.selection([
('normal','Free'),
('structured','Structured')
], 'Communication Type', required=True,
states={
'sent': [('readonly', True)],
'done': [('readonly', True)]
},
),
}
_defaults = {
'export_state': lambda *a: 'draft',
'date_done': lambda *a: False,
}
payment_line()
class payment_order(osv.osv):
'''
Enable extra state for payment exports
'''
_inherit = 'payment.order'
_columns = {
'date_planned': fields.date(
'Scheduled date if fixed',
states={
'sent': [('readonly', True)],
'done': [('readonly', True)]
},
help='Select a date if you have chosen Preferred Date to be fixed.'
),
'reference': fields.char(
'Reference', size=128, required=True,
states={
'sent': [('readonly', True)],
'done': [('readonly', True)]
},
),
'mode': fields.many2one(
'payment.mode', 'Payment mode', select=True, required=True,
states={
'sent': [('readonly', True)],
'done': [('readonly', True)]
},
help='Select the Payment Mode to be applied.'
),
'state': fields.selection([
('draft', 'Draft'),
('open','Confirmed'),
('cancel','Cancelled'),
('sent', 'Sent'),
('done','Done'),
], 'State', select=True
),
'line_ids': fields.one2many(
'payment.line', 'order_id', 'Payment lines',
states={
'sent': [('readonly', True)],
'done': [('readonly', True)]
},
),
'user_id': fields.many2one(
'res.users','User', required=True,
states={
'sent': [('readonly', True)],
'done': [('readonly', True)]
},
),
'date_prefered': fields.selection([
('now', 'Directly'),
('due', 'Due date'),
('fixed', 'Fixed date')
], "Preferred date", change_default=True, required=True,
states={
'sent': [('readonly', True)],
'done': [('readonly', True)]
},
help=("Choose an option for the Payment Order:'Fixed' stands for a "
"date specified by you.'Directly' stands for the direct "
"execution.'Due date' stands for the scheduled date of "
"execution."
)
),
}
def set_to_draft(self, cr, uid, ids, *args):
cr.execute("UPDATE payment_line "
"SET export_state = 'draft' "
"WHERE order_id in (%s)" % (
','.join(map(str, ids))
))
return super(payment_order, self).set_to_draft(
cr, uid, ids, *args
)
def action_sent(self, cr, uid, ids, *args):
cr.execute("UPDATE payment_line "
"SET export_state = 'sent' "
"WHERE order_id in (%s)" % (
','.join(map(str, ids))
))
return True
def set_done(self, cr, uid, id, *args):
'''
Extend standard transition to update childs as well.
'''
cr.execute("UPDATE payment_line "
"SET export_state = 'done', date_done = '%s' "
"WHERE order_id = %s" % (
time.strftime('%Y-%m-%d'),
self.id
))
return super(payment_order, self).set_done(
cr, uid, id, *args
)
payment_order()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

32
account_banking_demo.xml Normal file
View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<record model="res.partner" id="partner_demo1">
<field name="name">Tiny S.p.r.l</field>
</record>
<record model="res.partner.bank" id="partner_bank1">
<field name="acc_number">301915554082</field>
<field name="state">bank</field>
<field name="partner_id" ref="partner_demo1"></field>
</record>
<record model="res.partner" id="partner_demo2">
<field name="name">The-design Company</field>
</record>
<record model="res.partner.bank" id="partner_bank2">
<field name="acc_number">050000000017</field>
<field name="state">iban</field>
<field name="partner_id" ref="partner_demo2"></field>
</record>
<!-- <record model="res.partner.bank" id="partner_agrolait">
<field name="acc_number">301915554082</field>
<field name="partner_id" ref="base.res_partner_agrolait"/>
</record>
<record model="res.partner.bank" id="res_partner_10">
<field name="acc_number">050000000017</field>
<field name="partner_id" ref="base.res_partner_10"/>
</record>-->
</data>
</openerp>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) EduSense BV <http://www.edusense.nl>
All rights reserved.
The licence is in the file __terp__.py
-->
<openerp>
<data>
<wizard id="wizard_account_banking_import_file"
string="Import Bank Statements File"
model="account.bank.statement"
name="account_banking.banking_import"
/>
</data>
</openerp>

211
account_banking_view.xml Normal file
View File

@@ -0,0 +1,211 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) EduSense BV <http://www.edusense.nl>
All rights reserved.
The licence is in the file __terp__.py
-->
<openerp>
<data>
<!-- Create new submenu in finance/periodical processing -->
<menuitem name="Banking"
id="account_banking.menu_finance_banking_actions"
parent="account.menu_finance_periodical_processing"
/>
<!-- Create new submenu in finance/configuration -->
<menuitem name="Banking"
id="account_banking.menu_finance_banking_settings"
parent="account.menu_finance_configuration"
/>
<!-- Create new view on default journals for bank accounts -->
<record model="ir.ui.view" id="view_banking_account_settings_form">
<field name="name">account.banking.account.settings.form</field>
<field name="model">account.banking.account.settings</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Default Import Settings for Bank Account">
<field name="company_id" />
<separator string="Bank Account Details" colspan="4" />
<field name="partner_bank_id" /> <!-- Needs fiddling... domain="[('partner_id','=',company_id.partner_id)]"/-->
<field name="journal_id" domain="[('type','=','cash')]" />
<separator string="Default Accounts for Unknown Movements" colspan="4" />
<field name="default_credit_account_id" />
<field name="default_debit_account_id" />
</form>
</field>
</record>
<record model="ir.ui.view" id="view_banking_account_settings_tree">
<field name="name">account.banking.account.settings.tree</field>
<field name="model">account.banking.account.settings</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Default Import Settings for Bank Account">
<field name="company_id" />
<field name="partner_bank_id" /> <!-- Needs fiddling... domain="[('partner_id','=',company_id.partner_id)]"/-->
<field name="journal_id" domain="[('type','=','cash')]" />
</tree>
</field>
</record>
<record model="ir.actions.act_window" id="action_account_banking_journals">
<field name="name">Default Import Settings for Bank Accounts</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">account.banking.account.settings</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<!-- Create new submenu for finance configuration -->
<menuitem name="Default Import Settings for Bank Accounts"
id="menu_action_account_banking_bank_journals"
parent="account_banking.menu_finance_banking_settings"
action="action_account_banking_journals"
sequence="12"
/>
<!-- Create new view on imported statement files -->
<record model="ir.ui.view" id="view_account_banking_imported_file_form">
<field name="name">account.banking.imported.file.form</field>
<field name="model">account.banking.imported.file</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Imported Bank Statements">
<notebook colspan="4">
<page string="Import Details">
<field name="company_id" />
<field name="date" />
<field name="user_id" />
<field name="state" />
<field name="file"/>
<field name="format" />
</page>
<page string="Statements">
<field name="statement_ids" colspan="4"/>
</page>
<page string="Import Log">
<field name="log" colspan="4"/>
</page>
</notebook>
</form>
</field>
</record>
<record model="ir.ui.view" id="view_account_banking_imported_file_tree">
<field name="name">account.banking.imported.file.tree</field>
<field name="model">account.banking.imported.file</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Imported Bank Statements Files" colors="red:state=='error';blue:state=='unfinished'">
<field name="company_id" />
<field name="date" />
<field name="user_id" />
<field name="state" />
</tree>
</field>
</record>
<record model="ir.actions.act_window" id="action_account_banking_imported_files">
<field name="name">Imported Bank Statements Files</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">account.banking.imported.file</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<!-- Add a menu item for it -->
<menuitem name="Imported Bank Statements Files"
id="menu_action_account_banking_imported_files"
parent="account_banking.menu_finance_banking_actions"
action="action_account_banking_imported_files"
sequence="12"
/>
<!-- Add the import wizard to the menu -->
<menuitem name="Import Bank Statements File"
id="menu_account_banking_import_wizard"
parent="account_banking.menu_finance_banking_actions"
type="wizard"
action="wizard_account_banking_import_file"
sequence="15"/>
<!-- Create right menu entry to see statements -->
<act_window name="Bank Statements File"
domain="[('banking_id', '=', active_id)]"
res_model="account.banking.imported.file"
src_model="account.bank.statement"
view_type="form"
view_mode="tree,form"
id="act_account_payment_account_bank_statement"/>
<!-- Move period_id from bank_statement form to bank_statement_line form
-->
<record id="view_banking_bank_statement_tree_1" model="ir.ui.view">
<field name="name">account.bank.statement.tree.banking</field>
<field name="inherit_id" ref="account.view_bank_statement_tree" />
<field name="model">account.bank.statement</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<field name="period_id" position="replace"/>
</field>
</record>
<record id="view_banking_bank_statement_form_1" model="ir.ui.view">
<field name="name">account.bank.statement.form.banking-1</field>
<field name="inherit_id" ref="account.view_bank_statement_form" />
<field name="model">account.bank.statement</field>
<field name="type">form</field>
<field name="arch" type="xml">
<field name="period_id" position="replace"/>
</field>
</record>
<record id="view_banking_bank_statement_form_2" model="ir.ui.view">
<field name="name">account.bank.statement.form.banking-2</field>
<field name="inherit_id" ref="account.view_bank_statement_form" />
<field name="model">account.bank.statement</field>
<field name="type">form</field>
<field name="arch" type="xml">
<xpath expr="/form/notebook/page[@string='Entry encoding']/field/tree/field[@name='type']" position="after">
<field name="period_id"/>
</xpath>
</field>
</record>
<record id="view_banking_bank_statement_form_3" model="ir.ui.view">
<field name="name">account.bank.statement.form.banking-3</field>
<field name="inherit_id" ref="account.view_bank_statement_form" />
<field name="model">account.bank.statement</field>
<field name="type">form</field>
<field name="arch" type="xml">
<xpath expr="/form/notebook/page[@string='Entry encoding']/field/form/field[@name='type']" position="after">
<field name="period_id"/>
</xpath>
</field>
</record>
<!-- Reset trigger on button_confirm to the trigger code in this module -->
<record id="view_banking_bank_statement_form_4" model="ir.ui.view">
<field name="name">account.bank.statement.form.banking-4</field>
<field name="inherit_id" ref="account.view_bank_statement_form" />
<field name="model">account.bank.statement</field>
<field name="type">form</field>
<field name="arch" type="xml">
<button name="button_confirm" position="replace">
<button name="button_confirm" states="draft" string="Confirm" type="object"/>
</button>
</field>
</record>
<!-- Make buttons on payment order sensitive for 'Sent' state -->
<record id="view_banking_payment_order_form_1" model="ir.ui.view">
<field name="name">account.payment.order.form.banking-1</field>
<field name="inherit_id" ref="account_payment.view_payment_order_form" />
<field name="model">payment.order</field>
<field name="type">form</field>
<field name="arch" type="xml">
<xpath expr="/form/button[@string='Select Invoices to Pay']"
position="replace">
<button name="%(account_payment.wizard_populate_payment)s"
colspan="2" type="action" states="draft,open"
string="Select Invoices to Pay"
/>
</xpath>
</field>
</record>
</data>
</openerp>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) EduSense BV <http://www.edusense.nl>
All rights reserved.
The licence is in the file __terp__.py
-->
<openerp>
<data>
<!-- New activity for workflow payment order: sent -->
<record id="act_sent" model="workflow.activity">
<field name="name">sent</field>
<field name="wkf_id" ref="account_payment.wkf_payment_order"/>
<field name="action">action_sent()
write({'state':'sent'})</field>
<field name="kind">function</field>
</record>
<!-- Add new transition sent -> done -->
<record id="trans_sent_done" model="workflow.transition">
<field name="act_from" ref="act_sent"/>
<field name="act_to" ref="account_payment.act_done"/>
<field name="signal">done</field>
</record>
<!-- Rewrite existing open -> done transition to include 'sent' -->
<record id="account_payment.trans_open_done" model="workflow.transition">
<field name="act_from" ref="account_payment.act_open"/>
<field name="act_to" ref="act_sent"/>
<field name="signal">sent</field>
</record>
</data>
</openerp>

562
i18n/account_banking.pot Normal file
View File

@@ -0,0 +1,562 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * account_banking
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 5.0.7\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2010-01-08 15:30:50+0000\n"
"PO-Revision-Date: 2010-01-08 15:30:50+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: account_banking
#: wizard_view:account_banking.banking_import,import:0
msgid "Results:"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Number of errors found"
msgstr ""
#. module: account_banking
#: wizard_view:account_banking.banking_import,init:0
msgid "Select the processing details:"
msgstr ""
#. module: account_banking
#: constraint:ir.actions.act_window:0
msgid "Invalid model name in the action definition."
msgstr ""
#. module: account_banking
#: wizard_button:account_banking.banking_import,import,open:0
msgid "_Open Statement"
msgstr ""
#. module: account_banking
#: field:payment.line,date_done:0
msgid "Date Confirmed"
msgstr ""
#. module: account_banking
#: wizard_button:account_banking.banking_import,import,end:0
msgid "_Close"
msgstr ""
#. module: account_banking
#: model:ir.model,name:account_banking.model_account_banking_account_settings
msgid "Default Journal for Bank Account"
msgstr ""
#. module: account_banking
#: wizard_field:account_banking.banking_import,init,file:0
msgid "Statements File"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "More than one bank account was found with the same number %(account_no)s"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Total number of transactions"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "Account move line \"%s\" is not valid"
msgstr ""
#. module: account_banking
#: help:account.banking.account.settings,default_debit_account_id:0
msgid "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."
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "Bank account %(account_no)s was not found in the database"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Number of transactions skipped due to errors"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "The expected balance (%.2f) is different '\n"
" 'than the computed one. (%.2f)"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Statement for account %(bank_account)s uses different '\n"
" 'currency than the defined bank journal."
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Number of statements skipped due to errors"
msgstr ""
#. module: account_banking
#: view:account.banking.account.settings:0
msgid "Default Import Settings for Bank Account"
msgstr ""
#. module: account_banking
#: help:account.banking.account.settings,default_credit_account_id:0
msgid "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."
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Unable to import parser %(parser)s. Parser class not found."
msgstr ""
#. module: account_banking
#: selection:payment.line,export_state:0
msgid "Cancelled"
msgstr ""
#. module: account_banking
#: view:account.banking.imported.file:0
#: field:account.banking.imported.file,statement_ids:0
msgid "Statements"
msgstr ""
#. module: account_banking
#: field:account.banking.account.settings,default_debit_account_id:0
msgid "Default debit account"
msgstr ""
#. module: account_banking
#: wizard_button:account_banking.banking_import,init,end:0
msgid "_Cancel"
msgstr ""
#. module: account_banking
#: selection:payment.line,export_state:0
msgid "Draft"
msgstr ""
#. module: account_banking
#: field:account.banking.imported.file,date:0
msgid "Import Date"
msgstr ""
#. module: account_banking
#: selection:payment.line,export_state:0
msgid "Confirmed"
msgstr ""
#. module: account_banking
#: view:account.banking.account.settings:0
msgid "Default Accounts for Unknown Movements"
msgstr ""
#. module: account_banking
#: view:account.bank.statement:0
msgid "Confirm"
msgstr ""
#. module: account_banking
#: field:account.banking.account.settings,default_credit_account_id:0
msgid "Default credit account"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Statements found for account %(bank_account)s, '\n"
" 'but no default journal was defined."
msgstr ""
#. module: account_banking
#: field:account.bank.statement.line,international:0
msgid "International Transaction"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "Please verify that an account is defined in the journal."
msgstr ""
#. module: account_banking
#: field:account.bank.statement.line,trans:0
msgid "Bank Transaction ID"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Statement %(id)s known - skipped"
msgstr ""
#. module: account_banking
#: selection:payment.line,export_state:0
msgid "Sent"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "Error !"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "The account entries lines are not in valid state."
msgstr ""
#. module: account_banking
#: model:ir.actions.act_window,name:account_banking.action_account_banking_journals
#: model:ir.ui.menu,name:account_banking.menu_action_account_banking_bank_journals
msgid "Default Import Settings for Bank Accounts"
msgstr ""
#. module: account_banking
#: model:ir.actions.wizard,name:account_banking.wizard_account_banking_import_file
#: model:ir.ui.menu,name:account_banking.menu_account_banking_import_wizard
msgid "Import Bank Statements File"
msgstr ""
#. module: account_banking
#: help:account_banking.banking_import,init,file:0
msgid "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."
msgstr ""
#. module: account_banking
#: constraint:ir.ui.view:0
msgid "Invalid XML for View Architecture!"
msgstr ""
#. module: account_banking
#: view:account.banking.imported.file:0
msgid "Imported Bank Statements"
msgstr ""
#. module: account_banking
#: wizard_view:account_banking.banking_import,import:0
#: wizard_view:account_banking.banking_import,init:0
msgid "Import Bank Transactions File"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "Account %(account_no)s is not owned by %(partner)s"
msgstr ""
#. module: account_banking
#: wizard_button:account_banking.banking_import,init,import:0
msgid "_Ok"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "More then one possible match found for partner with name %(name)s"
msgstr ""
#. module: account_banking
#: field:account.banking.imported.file,state:0
#: field:payment.line,export_state:0
msgid "State"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "ERROR!"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "No suitable period found for date %(date)s"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "Multiple overlapping periods for date %(date)s"
msgstr ""
#. module: account_banking
#: field:account.banking.account.settings,company_id:0
#: field:account.banking.imported.file,company_id:0
#: wizard_field:account_banking.banking_import,init,company:0
msgid "Company"
msgstr ""
#. module: account_banking
#: wizard_field:account_banking.banking_import,import,log:0
msgid "Log"
msgstr ""
#. module: account_banking
#: field:account.banking.imported.file,file:0
msgid "Raw Data"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "Configration Error !"
msgstr ""
#. module: account_banking
#: model:ir.module.module,description:account_banking.module_meta_information
msgid "\n"
" Module to do banking.\n"
"\n"
" This modules tries to combine all current banking import and export\n"
" schemes. Rationale for this is that it is quite common to have foreign\n"
" bank account numbers next to national bank account numbers. The current\n"
" approach, which hides the national banking interface schemes in the\n"
" l10n_xxx modules, makes it very difficult to use these simultanious.\n"
" A more banking oriented approach seems more logical and cleaner.\n"
"\n"
" Changes to default OpenERP:\n"
"\n"
" * Puts focus on the real life messaging with banks:\n"
" + Bank statement lines upgraded to independent bank transactions.\n"
" + Banking statements have no special accountancy meaning, they're just\n"
" message envelopes for a number of bank transactions.\n"
" + Bank statements can be either encoded by hand to reflect the document\n"
" version of Bank Statements, or created as an optional side effect of\n"
" importing Bank Transactions.\n"
"\n"
" * Preparations for SEPA:\n"
" + IBAN accounts are the standard in the SEPA countries\n"
" + local accounts are derived from SEPA (excluding Turkey) but are\n"
" considered to be identical to the corresponding SEPA account.\n"
" + Banks are identified with either Country + Bank code + Branch code or BIC\n"
" + Each bank can have its own pace in introducing SEPA into their\n"
" communication with their customers.\n"
" + National online databases can be used to convert BBAN's to IBAN's.\n"
"\n"
" * Adds dropin extensible import facility for bank communication in:\n"
" + MultiBank (NL) format transaction files,\n"
" - (todo) MT940 (Swift) format transaction files,\n"
" - (todo) CODA (BE) format transaction files,\n"
" - (wish) SEPA Credits (ISO 200022) messages,\n"
"\n"
" * Extends payments for digital banking:\n"
" + Adapted workflow in payments to reflect banking operations\n"
" + Relies on account_payment mechanics to extend with export generators.\n"
" - ClieOp3 (NL) payment and direct debit orders files available as\n"
" account_banking_nl_clieop\n"
" - (wish) BTL91 (NL) payment orders files (no format description available),\n"
" - (wish) SEPA Direct Debits (ISO 200022) messages\n"
"\n"
" * Additional features for the import/export mechanism:\n"
" + Automatic matching and creation of bank accounts, banks and partners,\n"
" during import of statements.\n"
" + Automatic matching with invoices and payments.\n"
" + Sound import mechanism, allowing multiple imports of the same\n"
" transactions repeated over multiple files.\n"
" + Journal configuration per bank account.\n"
" + Business logic and format parsing strictly separated to ease the\n"
" development of new parsers.\n"
" + No special configuration needed for the parsers, new parsers are\n"
" recognized and made available at server (re)start.\n"
" "
msgstr ""
#. module: account_banking
#: model:ir.actions.act_window,name:account_banking.act_account_payment_account_bank_statement
msgid "Bank Statements File"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/parsers/models.py:0
#, python-format
msgid "This is a stub. Please implement your own."
msgstr ""
#. module: account_banking
#: view:account.banking.imported.file:0
#: model:ir.actions.act_window,name:account_banking.action_account_banking_imported_files
#: model:ir.ui.menu,name:account_banking.menu_action_account_banking_imported_files
msgid "Imported Bank Statements Files"
msgstr ""
#. module: account_banking
#: field:account.bank.statement,banking_id:0
msgid "Imported File"
msgstr ""
#. module: account_banking
#: view:account.banking.imported.file:0
#: field:account.banking.imported.file,log:0
msgid "Import Log"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "The imported statements appear to be invalid! Check your file."
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Number of statements loaded"
msgstr ""
#. module: account_banking
#: model:ir.ui.menu,name:account_banking.menu_finance_banking_actions
#: model:ir.ui.menu,name:account_banking.menu_finance_banking_settings
msgid "Banking"
msgstr ""
#. module: account_banking
#: selection:account.banking.imported.file,state:0
msgid "Error"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "Unable to reconcile entry \"%s\": %.2f"
msgstr ""
#. module: account_banking
#: view:account.banking.imported.file:0
msgid "Import Details"
msgstr ""
#. module: account_banking
#: field:account.bank.statement.line,period_id:0
msgid "Period"
msgstr ""
#. module: account_banking
#: selection:payment.line,export_state:0
msgid "Done"
msgstr ""
#. module: account_banking
#: view:payment.order:0
msgid "Select Invoices to Pay"
msgstr ""
#. module: account_banking
#: field:account.banking.imported.file,user_id:0
msgid "Responsible User"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "The statement balance is incorrect !\n"
msgstr ""
#. module: account_banking
#: constraint:ir.model:0
msgid "The Object name must start with x_ and not contain any special character !"
msgstr ""
#. module: account_banking
#: selection:account.banking.imported.file,state:0
msgid "Unfinished"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Statements found for unknown account %(bank_account)s"
msgstr ""
#. module: account_banking
#: model:ir.module.module,shortdesc:account_banking.module_meta_information
msgid "Account Banking"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Total number of statements"
msgstr ""
#. module: account_banking
#: field:account.banking.imported.file,format:0
#: wizard_field:account_banking.banking_import,init,parser:0
msgid "File Format"
msgstr ""
#. module: account_banking
#: field:account.banking.account.settings,journal_id:0
msgid "Journal"
msgstr ""
#. module: account_banking
#: selection:account.banking.imported.file,state:0
msgid "Finished"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "No suitable fiscal year found for company %(company_name)s"
msgstr ""
#. module: account_banking
#: view:account.banking.account.settings:0
msgid "Bank Account Details"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Unable to link transaction %(trans)s to invoice: '\n"
" '%(no_candidates)s candidates found; can\'t choose."
msgstr ""
#. module: account_banking
#: field:account.bank.statement.line,partner_bank_id:0
#: field:account.banking.account.settings,partner_bank_id:0
msgid "Bank Account"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Number of transactions loaded"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "Multiple overlapping fiscal years found for date %(date)s"
msgstr ""
#. module: account_banking
#: model:ir.model,name:account_banking.model_account_banking_imported_file
msgid "Imported Bank Statements File"
msgstr ""

562
i18n/en_US.po Normal file
View File

@@ -0,0 +1,562 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * account_banking
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 5.0.7\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2010-01-08 15:30:50+0000\n"
"PO-Revision-Date: 2010-01-08 15:30:50+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: account_banking
#: wizard_view:account_banking.banking_import,import:0
msgid "Results:"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Number of errors found"
msgstr ""
#. module: account_banking
#: wizard_view:account_banking.banking_import,init:0
msgid "Select the processing details:"
msgstr ""
#. module: account_banking
#: constraint:ir.actions.act_window:0
msgid "Invalid model name in the action definition."
msgstr ""
#. module: account_banking
#: wizard_button:account_banking.banking_import,import,open:0
msgid "_Open Statement"
msgstr ""
#. module: account_banking
#: field:payment.line,date_done:0
msgid "Date Confirmed"
msgstr ""
#. module: account_banking
#: wizard_button:account_banking.banking_import,import,end:0
msgid "_Close"
msgstr ""
#. module: account_banking
#: model:ir.model,name:account_banking.model_account_banking_account_settings
msgid "Default Journal for Bank Account"
msgstr ""
#. module: account_banking
#: wizard_field:account_banking.banking_import,init,file:0
msgid "Statements File"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "More than one bank account was found with the same number %(account_no)s"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Total number of transactions"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "Account move line \"%s\" is not valid"
msgstr ""
#. module: account_banking
#: help:account.banking.account.settings,default_debit_account_id:0
msgid "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."
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "Bank account %(account_no)s was not found in the database"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Number of transactions skipped due to errors"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "The expected balance (%.2f) is different '\n"
" 'than the computed one. (%.2f)"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Statement for account %(bank_account)s uses different '\n"
" 'currency than the defined bank journal."
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Number of statements skipped due to errors"
msgstr ""
#. module: account_banking
#: view:account.banking.account.settings:0
msgid "Default Import Settings for Bank Account"
msgstr ""
#. module: account_banking
#: help:account.banking.account.settings,default_credit_account_id:0
msgid "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."
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Unable to import parser %(parser)s. Parser class not found."
msgstr ""
#. module: account_banking
#: selection:payment.line,export_state:0
msgid "Cancelled"
msgstr ""
#. module: account_banking
#: view:account.banking.imported.file:0
#: field:account.banking.imported.file,statement_ids:0
msgid "Statements"
msgstr ""
#. module: account_banking
#: field:account.banking.account.settings,default_debit_account_id:0
msgid "Default debit account"
msgstr ""
#. module: account_banking
#: wizard_button:account_banking.banking_import,init,end:0
msgid "_Cancel"
msgstr ""
#. module: account_banking
#: selection:payment.line,export_state:0
msgid "Draft"
msgstr ""
#. module: account_banking
#: field:account.banking.imported.file,date:0
msgid "Import Date"
msgstr ""
#. module: account_banking
#: selection:payment.line,export_state:0
msgid "Confirmed"
msgstr ""
#. module: account_banking
#: view:account.banking.account.settings:0
msgid "Default Accounts for Unknown Movements"
msgstr ""
#. module: account_banking
#: view:account.bank.statement:0
msgid "Confirm"
msgstr ""
#. module: account_banking
#: field:account.banking.account.settings,default_credit_account_id:0
msgid "Default credit account"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Statements found for account %(bank_account)s, '\n"
" 'but no default journal was defined."
msgstr ""
#. module: account_banking
#: field:account.bank.statement.line,international:0
msgid "International Transaction"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "Please verify that an account is defined in the journal."
msgstr ""
#. module: account_banking
#: field:account.bank.statement.line,trans:0
msgid "Bank Transaction ID"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Statement %(id)s known - skipped"
msgstr ""
#. module: account_banking
#: selection:payment.line,export_state:0
msgid "Sent"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "Error !"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "The account entries lines are not in valid state."
msgstr ""
#. module: account_banking
#: model:ir.actions.act_window,name:account_banking.action_account_banking_journals
#: model:ir.ui.menu,name:account_banking.menu_action_account_banking_bank_journals
msgid "Default Import Settings for Bank Accounts"
msgstr ""
#. module: account_banking
#: model:ir.actions.wizard,name:account_banking.wizard_account_banking_import_file
#: model:ir.ui.menu,name:account_banking.menu_account_banking_import_wizard
msgid "Import Bank Statements File"
msgstr ""
#. module: account_banking
#: help:account_banking.banking_import,init,file:0
msgid "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."
msgstr ""
#. module: account_banking
#: constraint:ir.ui.view:0
msgid "Invalid XML for View Architecture!"
msgstr ""
#. module: account_banking
#: view:account.banking.imported.file:0
msgid "Imported Bank Statements"
msgstr ""
#. module: account_banking
#: wizard_view:account_banking.banking_import,import:0
#: wizard_view:account_banking.banking_import,init:0
msgid "Import Bank Transactions File"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "Account %(account_no)s is not owned by %(partner)s"
msgstr ""
#. module: account_banking
#: wizard_button:account_banking.banking_import,init,import:0
msgid "_Ok"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "More then one possible match found for partner with name %(name)s"
msgstr ""
#. module: account_banking
#: field:account.banking.imported.file,state:0
#: field:payment.line,export_state:0
msgid "State"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "ERROR!"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "No suitable period found for date %(date)s"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "Multiple overlapping periods for date %(date)s"
msgstr ""
#. module: account_banking
#: field:account.banking.account.settings,company_id:0
#: field:account.banking.imported.file,company_id:0
#: wizard_field:account_banking.banking_import,init,company:0
msgid "Company"
msgstr ""
#. module: account_banking
#: wizard_field:account_banking.banking_import,import,log:0
msgid "Log"
msgstr ""
#. module: account_banking
#: field:account.banking.imported.file,file:0
msgid "Raw Data"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "Configration Error !"
msgstr ""
#. module: account_banking
#: model:ir.module.module,description:account_banking.module_meta_information
msgid "\n"
" Module to do banking.\n"
"\n"
" This modules tries to combine all current banking import and export\n"
" schemes. Rationale for this is that it is quite common to have foreign\n"
" bank account numbers next to national bank account numbers. The current\n"
" approach, which hides the national banking interface schemes in the\n"
" l10n_xxx modules, makes it very difficult to use these simultanious.\n"
" A more banking oriented approach seems more logical and cleaner.\n"
"\n"
" Changes to default OpenERP:\n"
"\n"
" * Puts focus on the real life messaging with banks:\n"
" + Bank statement lines upgraded to independent bank transactions.\n"
" + Banking statements have no special accountancy meaning, they're just\n"
" message envelopes for a number of bank transactions.\n"
" + Bank statements can be either encoded by hand to reflect the document\n"
" version of Bank Statements, or created as an optional side effect of\n"
" importing Bank Transactions.\n"
"\n"
" * Preparations for SEPA:\n"
" + IBAN accounts are the standard in the SEPA countries\n"
" + local accounts are derived from SEPA (excluding Turkey) but are\n"
" considered to be identical to the corresponding SEPA account.\n"
" + Banks are identified with either Country + Bank code + Branch code or BIC\n"
" + Each bank can have its own pace in introducing SEPA into their\n"
" communication with their customers.\n"
" + National online databases can be used to convert BBAN's to IBAN's.\n"
"\n"
" * Adds dropin extensible import facility for bank communication in:\n"
" + MultiBank (NL) format transaction files,\n"
" - (todo) MT940 (Swift) format transaction files,\n"
" - (todo) CODA (BE) format transaction files,\n"
" - (wish) SEPA Credits (ISO 200022) messages,\n"
"\n"
" * Extends payments for digital banking:\n"
" + Adapted workflow in payments to reflect banking operations\n"
" + Relies on account_payment mechanics to extend with export generators.\n"
" - ClieOp3 (NL) payment and direct debit orders files available as\n"
" account_banking_nl_clieop\n"
" - (wish) BTL91 (NL) payment orders files (no format description available),\n"
" - (wish) SEPA Direct Debits (ISO 200022) messages\n"
"\n"
" * Additional features for the import/export mechanism:\n"
" + Automatic matching and creation of bank accounts, banks and partners,\n"
" during import of statements.\n"
" + Automatic matching with invoices and payments.\n"
" + Sound import mechanism, allowing multiple imports of the same\n"
" transactions repeated over multiple files.\n"
" + Journal configuration per bank account.\n"
" + Business logic and format parsing strictly separated to ease the\n"
" development of new parsers.\n"
" + No special configuration needed for the parsers, new parsers are\n"
" recognized and made available at server (re)start.\n"
" "
msgstr ""
#. module: account_banking
#: model:ir.actions.act_window,name:account_banking.act_account_payment_account_bank_statement
msgid "Bank Statements File"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/parsers/models.py:0
#, python-format
msgid "This is a stub. Please implement your own."
msgstr ""
#. module: account_banking
#: view:account.banking.imported.file:0
#: model:ir.actions.act_window,name:account_banking.action_account_banking_imported_files
#: model:ir.ui.menu,name:account_banking.menu_action_account_banking_imported_files
msgid "Imported Bank Statements Files"
msgstr ""
#. module: account_banking
#: field:account.bank.statement,banking_id:0
msgid "Imported File"
msgstr ""
#. module: account_banking
#: view:account.banking.imported.file:0
#: field:account.banking.imported.file,log:0
msgid "Import Log"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "The imported statements appear to be invalid! Check your file."
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Number of statements loaded"
msgstr ""
#. module: account_banking
#: model:ir.ui.menu,name:account_banking.menu_finance_banking_actions
#: model:ir.ui.menu,name:account_banking.menu_finance_banking_settings
msgid "Banking"
msgstr ""
#. module: account_banking
#: selection:account.banking.imported.file,state:0
msgid "Error"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "Unable to reconcile entry \"%s\": %.2f"
msgstr ""
#. module: account_banking
#: view:account.banking.imported.file:0
msgid "Import Details"
msgstr ""
#. module: account_banking
#: field:account.bank.statement.line,period_id:0
msgid "Period"
msgstr ""
#. module: account_banking
#: selection:payment.line,export_state:0
msgid "Done"
msgstr ""
#. module: account_banking
#: view:payment.order:0
msgid "Select Invoices to Pay"
msgstr ""
#. module: account_banking
#: field:account.banking.imported.file,user_id:0
msgid "Responsible User"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "The statement balance is incorrect !\n"
msgstr ""
#. module: account_banking
#: constraint:ir.model:0
msgid "The Object name must start with x_ and not contain any special character !"
msgstr ""
#. module: account_banking
#: selection:account.banking.imported.file,state:0
msgid "Unfinished"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Statements found for unknown account %(bank_account)s"
msgstr ""
#. module: account_banking
#: model:ir.module.module,shortdesc:account_banking.module_meta_information
msgid "Account Banking"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Total number of statements"
msgstr ""
#. module: account_banking
#: field:account.banking.imported.file,format:0
#: wizard_field:account_banking.banking_import,init,parser:0
msgid "File Format"
msgstr ""
#. module: account_banking
#: field:account.banking.account.settings,journal_id:0
msgid "Journal"
msgstr ""
#. module: account_banking
#: selection:account.banking.imported.file,state:0
msgid "Finished"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "No suitable fiscal year found for company %(company_name)s"
msgstr ""
#. module: account_banking
#: view:account.banking.account.settings:0
msgid "Bank Account Details"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Unable to link transaction %(trans)s to invoice: '\n"
" '%(no_candidates)s candidates found; can\'t choose."
msgstr ""
#. module: account_banking
#: field:account.bank.statement.line,partner_bank_id:0
#: field:account.banking.account.settings,partner_bank_id:0
msgid "Bank Account"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Number of transactions loaded"
msgstr ""
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "Multiple overlapping fiscal years found for date %(date)s"
msgstr ""
#. module: account_banking
#: model:ir.model,name:account_banking.model_account_banking_imported_file
msgid "Imported Bank Statements File"
msgstr ""

607
i18n/nl_NL.po Normal file
View File

@@ -0,0 +1,607 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * account_banking
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 5.0.7\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2010-01-08 15:18:44+0000\n"
"PO-Revision-Date: 2010-01-08 15:18:44+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: account_banking
#: wizard_view:account_banking.banking_import,import:0
msgid "Results:"
msgstr "Resultaat:"
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Number of errors found"
msgstr "Aantal gevonden fouten"
#. module: account_banking
#: wizard_view:account_banking.banking_import,init:0
msgid "Select the processing details:"
msgstr "Kies de verwerkings-details:"
#. module: account_banking
#: constraint:ir.actions.act_window:0
msgid "Invalid model name in the action definition."
msgstr "Ongeldige naam in actie-definitie."
#. module: account_banking
#: wizard_button:account_banking.banking_import,import,open:0
msgid "_Open Statement"
msgstr "_Open bankafschriften"
#. module: account_banking
#: field:payment.line,date_done:0
msgid "Date Confirmed"
msgstr "Bevestigingsdatum"
#. module: account_banking
#: wizard_button:account_banking.banking_import,import,end:0
msgid "_Close"
msgstr "_Sluit"
#. module: account_banking
#: model:ir.model,name:account_banking.model_account_banking_account_settings
msgid "Default Journal for Bank Account"
msgstr "Standaard dagboek voor bankrekening"
#. module: account_banking
#: wizard_field:account_banking.banking_import,init,file:0
msgid "Statements File"
msgstr "Transactiebestand"
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "More than one bank account was found with the same number %(account_no)s"
msgstr "Meer dan één bankrekening gevonden met hetzelfde rekeningnummer %(account_no)s"
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Total number of transactions"
msgstr "Totaal aantal transacties"
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "Account move line \"%s\" is not valid"
msgstr "Boekingsregel \"%s\" is onjuist."
#. module: account_banking
#: help:account.banking.account.settings,default_debit_account_id:0
msgid "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."
msgstr "De rekening waarop geboekt moet worden bij onverwachte betalingen. Dit kan nodig zijn als een klant vooruit betaalt of wanneer er geen overeenkomende factuur gevonden kan worden. Merk op dat u altijd boekingen kunt corrigeren voordat u deze bevestigt."
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "Bank account %(account_no)s was not found in the database"
msgstr "Bankrekening %(account)s niet gevonden in de database"
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Number of transactions skipped due to errors"
msgstr "Aantal overgeslagen transacties als gevolg van fouten"
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "The expected balance (%.2f) is different '\n"
" 'than the computed one. (%.2f)"
msgstr "Het verwachte saldo (%.2f) wijkt af van het berekende- (%.2f)."
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Statement for account %(bank_account)s uses different '\n"
" 'currency than the defined bank journal."
msgstr "Afschrift voor bankrekening %(account)s gebruik andere valuta dan opgegeven in het bankdagboek."
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Number of statements skipped due to errors"
msgstr "Aantal afschriften overgeslagen door fouten"
#. module: account_banking
#: view:account.banking.account.settings:0
msgid "Default Import Settings for Bank Account"
msgstr "Standaardinstellingen voor bankrekeningen"
#. module: account_banking
#: help:account.banking.account.settings,default_credit_account_id:0
msgid "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."
msgstr "De te gebruiken rekening bij onverwachte betalingen. Dit kan voorkomen indien een incasso-opdracht door een klant is geannuleerd, of wanneer er geen overeenkomende betaling kan worden gevonden. Merk op dat u altijd boekingen kunt corrigeren voordat u deze bevestigt."
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Unable to import parser %(parser)s. Parser class not found."
msgstr "Niet in staat parser %(parser)s te importeren. Parser class niet gevonden."
#. module: account_banking
#: selection:payment.line,export_state:0
msgid "Cancelled"
msgstr "Geannuleerd"
#. module: account_banking
#: view:account.banking.imported.file:0
#: field:account.banking.imported.file,statement_ids:0
msgid "Statements"
msgstr "Afschriften"
#. module: account_banking
#: field:account.banking.account.settings,default_debit_account_id:0
msgid "Default debit account"
msgstr "Standaard debet-rekening"
#. module: account_banking
#. module: account_banking
#: field:account.banking.imported.file,date:0
msgid "Import Date"
msgstr "Importdatum"
#. module: account_banking
#: selection:payment.line,export_state:0
msgid "Confirmed"
msgstr "Bevestigd"
#. module: account_banking
#: view:account.banking.account.settings:0
msgid "Default Accounts for Unknown Movements"
msgstr "Standaard rekeningen voor onverwachte mutaties"
#. module: account_banking
#: view:account.bank.statement:0
msgid "Confirm"
msgstr "Bevestig"
#. module: account_banking
#: field:account.banking.account.settings,default_credit_account_id:0
msgid "Default credit account"
msgstr "Standaard credit-rekening"
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Statements found for account %(bank_account)s, '\n"
" 'but no default journal was defined."
msgstr "Afschriften gevonden voor bankrekening %(bank_account)s, maar geen gedefinieerd dagboek gevonden hiervoor."
#. module: account_banking
#: field:account.bank.statement.line,international:0
msgid "International Transaction"
msgstr "Internationale transactie"
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "Please verify that an account is defined in the journal."
msgstr "Controleer alstublieft of een rekening is opgegeven in het journaal"
#. module: account_banking
#: field:account.bank.statement.line,trans:0
msgid "Bank Transaction ID"
msgstr "Transactie ID bank"
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Statement %(id)s known - skipped"
msgstr "Afschrift %(id)s al bekend - overgeslagen"
#. module: account_banking
#: selection:payment.line,export_state:0
msgid "Sent"
msgstr "Verzonden"
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "Error !"
msgstr "Fout !"
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "The account entries lines are not in valid state."
msgstr "De boekingsregels zijn niet geldig."
#. module: account_banking
#: model:ir.actions.act_window,name:account_banking.action_account_banking_journals
#: model:ir.ui.menu,name:account_banking.menu_action_account_banking_bank_journals
msgid "Default Import Settings for Bank Accounts"
msgstr "Standaardinstellingen voor bankrekeningen"
#. module: account_banking
#: model:ir.actions.wizard,name:account_banking.wizard_account_banking_import_file
#: model:ir.ui.menu,name:account_banking.menu_account_banking_import_wizard
msgid "Import Bank Statements File"
msgstr "Importeer bankafschrift bestand"
#. module: account_banking
#: help:account_banking.banking_import,init,file:0
msgid "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."
msgstr "Het te importeren transactiebestand. Let alstublieft op: hoewel het zondermeer veilig is om hetzelfde bestand meerdere keren te importeren of om in tijd overlappende bestanden te importeren, zijn er formaten die een ander nummeringsschema introduceren, wat tot problemen kan leiden.\n"
"\n"
"Om aan de veilige kant te blijven, importeer altijd transactiebestanden in hetzelfde formaat."
#. module: account_banking
#: constraint:ir.ui.view:0
msgid "Invalid XML for View Architecture!"
msgstr "Ongeldige XML voor overzicht"
#. module: account_banking
#: view:account.banking.imported.file:0
msgid "Imported Bank Statements"
msgstr "Geïmporteerde bankafschriften"
#. module: account_banking
#: wizard_view:account_banking.banking_import,import:0
#: wizard_view:account_banking.banking_import,init:0
msgid "Import Bank Transactions File"
msgstr "Importeer banktransacties-bestand"
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "Account %(account_no)s is not owned by %(partner)s"
msgstr "Rekening %(account_no)s is geen eigendom van %(partner)s"
#. module: account_banking
#: wizard_button:account_banking.banking_import,init,import:0
msgid "_Ok"
msgstr "_Ok"
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "More then one possible match found for partner with name %(name)s"
msgstr "Meer dan één mogelijke match gevonden voor partner met naam %(name)s"
#. module: account_banking
#: field:account.banking.imported.file,state:0
#: field:payment.line,export_state:0
msgid "State"
msgstr "Status"
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "No suitable period found for date %(date)s"
msgstr "Geen geschikte periode gevonden voor datum %(date)s"
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "Multiple overlapping periods for date %(date)s"
msgstr "Meerdere overlappende periodes gevonden voor datum %(date)s"
#. module: account_banking
#: field:account.banking.account.settings,company_id:0
#: field:account.banking.imported.file,company_id:0
#: wizard_field:account_banking.banking_import,init,company:0
msgid "Company"
msgstr "Bedrijf"
#. module: account_banking
#: wizard_field:account_banking.banking_import,import,log:0
msgid "Log"
msgstr "Logboek"
#. module: account_banking
#: field:account.banking.imported.file,file:0
msgid "Raw Data"
msgstr "Ruwe data"
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "Configration Error !"
msgstr "Instellingsfout!"
#. module: account_banking
#: model:ir.module.module,description:account_banking.module_meta_information
msgid "\n"
" Module to do banking.\n"
"\n"
" This modules tries to combine all current banking import and export\n"
" schemes. Rationale for this is that it is quite common to have foreign\n"
" bank account numbers next to national bank account numbers. The current\n"
" approach, which hides the national banking interface schemes in the\n"
" l10n_xxx modules, makes it very difficult to use these simultanious.\n"
" A more banking oriented approach seems more logical and cleaner.\n"
"\n"
" Changes to default OpenERP:\n"
"\n"
" * Puts focus on the real life messaging with banks:\n"
" + Bank statement lines upgraded to independent bank transactions.\n"
" + Banking statements have no special accountancy meaning, they're just\n"
" message envelopes for a number of bank transactions.\n"
" + Bank statements can be either encoded by hand to reflect the document\n"
" version of Bank Statements, or created as an optional side effect of\n"
" importing Bank Transactions.\n"
"\n"
" * Preparations for SEPA:\n"
" + IBAN accounts are the standard in the SEPA countries\n"
" + local accounts are derived from SEPA (excluding Turkey) but are\n"
" considered to be identical to the corresponding SEPA account.\n"
" + Banks are identified with either Country + Bank code + Branch code or BIC\n"
" + Each bank can have its own pace in introducing SEPA into their\n"
" communication with their customers.\n"
" + National online databases can be used to convert BBAN's to IBAN's.\n"
"\n"
" * Adds dropin extensible import facility for bank communication in:\n"
" + MultiBank (NL) format transaction files,\n"
" - (todo) MT940 (Swift) format transaction files,\n"
" - (todo) CODA (BE) format transaction files,\n"
" - (wish) SEPA Credits (ISO 200022) messages,\n"
"\n"
" * Extends payments for digital banking:\n"
" + Adapted workflow in payments to reflect banking operations\n"
" + Relies on account_payment mechanics to extend with export generators.\n"
" - ClieOp3 (NL) payment and direct debit orders files available as\n"
" account_banking_nl_clieop\n"
" - (wish) BTL91 (NL) payment orders files (no format description available),\n"
" - (wish) SEPA Direct Debits (ISO 200022) messages\n"
"\n"
" * Additional features for the import/export mechanism:\n"
" + Automatic matching and creation of bank accounts, banks and partners,\n"
" during import of statements.\n"
" + Automatic matching with invoices and payments.\n"
" + Sound import mechanism, allowing multiple imports of the same\n"
" transactions repeated over multiple files.\n"
" + Journal configuration per bank account.\n"
" + Business logic and format parsing strictly separated to ease the\n"
" development of new parsers.\n"
" + No special configuration needed for the parsers, new parsers are\n"
" recognized and made available at server (re)start.\n"
" "
msgstr " Module voor bankzaken.\n"
"\n"
" Deze module probeert alle bestaande bankimport- en -exportschema's\n"
" te combineren. Ratio hierachter is dat het vrij gebruikelijk is om\n"
" buitenlandse bankrekeningen te hebben naast nationale-. De huidige\n"
" benadering waarbij nationale bankinterfaces ondergebracht worden in\n"
" de l10n_xxx modules, maakt het zeer lastig om deze naast elkaar te\n"
" gebruiken. Een meer bank-geöriënteerde benadering lijkt logischer en\n"
" 'schoner'.\n"
"\n"
" Wijzigingen op standaard OpenERP:\n"
"\n"
" * Legt focus op berichtuitwisseling met banken:\n"
" + Bankafschriftregels opgewaardeerd naar onafhankelijke banktransacties.\n"
" + Bankafschriften hebben geen speciale accountancy-betekenis, ze zijn\n"
" slechts enveloppen voor een reeks banktransacties.\n"
" + Bankafschriften kunnen hetzij met de hand worden ingevoerd als projectie\n"
" van de papieren versie, of gemaakt worden als neveneffect van het\n"
" importeren van banktransacties.\n"
"\n"
" * Voorbereidingen voor SEPA:\n"
" + IBAN bankrekeningen zijn de standaard in de SEPA-landen\n"
" + lokale bankrekeningen worden afgeleid van SEPA (uitgezonderd Turkije)\n"
" maar worden beschouwd als identiek aan de corresponderende IBAN-rekening.\n"
" + Banken worden geïdentificeerd met hetzij land + bankcode + branchcode of BIC\n"
" - Each bank can have its own pace in introducing SEPA into their\n"
" communication with their customers.\n"
" + Nationale online databases kunnen gebruikt worden om BBAN's naar IBAN's te\n"
" converteren.\n"
"\n"
" * Geeft dropin uitbreidbare importvoorzieningen voor bankcommunicatie in:\n"
" + MultiBank (NL) formaat transactiebestanden,\n"
" - (todo) MT940 (Swift) formaat transactiebestanden,\n"
" - (todo) CODA (BE) formaat transactiebestanden,\n"
" - (wish) SEPA Credits (ISO 200022) berichten,\n"
"\n"
" * Breidt betalingen uit voor digitaal bankieren:\n"
" + Werkstroom in betalingen aangepast voor bank-operaties\n"
" + Bouwt op account_payment mechanieken voor uitbreidingen met export generatoren.\n"
" - ClieOp3 (NL) betalings- en incasso-opdrachten beschikbaar in de\n"
" account_banking_nl_clieop module\n"
" - (wish) BTL91 (NL) batalingsopdrachten (geen formaatbeschrijving beschikbaar),\n"
" - (wish) SEPA Direct Debits (ISO 200022) berichten\n"
"\n"
" * Toegevoegde mogelijkheden voor het import/export mechanisme:\n"
" + Automatische koppeling en aanmaken van bankrekeningen, banken en relaties\n"
" tijdens het importeren van transacties.\n"
" + Automatisch koppelen met facturen en betalingen.\n"
" + Solide importmechanisme dat meerdere imports van dezelfde transacties over\n"
" over meerdere bestanden toestaat.\n"
" + Dagboek-instellingen per bankrekening.\n"
" + Business logica en bestands-parsing strikt gescheiden om de ontwikkeling\n"
" van nieuwe parsers te vergemakkelijken\n"
" + Geen speciale configuratie nodig voor de parsers, nieuwe parsers worden\n"
" herkend en beschikbaar gemaakt voor gebuikers bij server(her)start.\n"
" "
#. module: account_banking
#: model:ir.actions.act_window,name:account_banking.act_account_payment_account_bank_statement
msgid "Bank Statements File"
msgstr "Bankafschrift bestand"
#. module: account_banking
#: code:addons/account_banking/parsers/models.py:0
#, python-format
msgid "This is a stub. Please implement your own."
msgstr "Dit is een stub. Maak alstublieft uw eigen versie."
#. module: account_banking
#: view:account.banking.imported.file:0
#: model:ir.actions.act_window,name:account_banking.action_account_banking_imported_files
#: model:ir.ui.menu,name:account_banking.menu_action_account_banking_imported_files
msgid "Imported Bank Statements Files"
msgstr "Geïmporteerde bankafschrift bestanden"
#. module: account_banking
#: field:account.bank.statement,banking_id:0
msgid "Imported File"
msgstr "Geïmporteerd bestand"
#. module: account_banking
#: view:account.banking.imported.file:0
#: field:account.banking.imported.file,log:0
msgid "Import Log"
msgstr "Import log"
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "The imported statements appear to be invalid! Check your file."
msgstr "De geïmporteerde afschriften lijken onjuist! Controleer uw bestand."
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Number of statements loaded"
msgstr "Aantal geladen afschriften"
#. module: account_banking
#: model:ir.ui.menu,name:account_banking.menu_finance_banking_actions
#: model:ir.ui.menu,name:account_banking.menu_finance_banking_settings
msgid "Banking"
msgstr "Bankzaken"
#. module: account_banking
#: selection:account.banking.imported.file,state:0
msgid "Error"
msgstr "Fout"
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "Unable to reconcile entry \"%s\": %.2f"
msgstr "Niet in staat boeking af te letteren \"%s\": %.2f"
#. module: account_banking
#: view:account.banking.imported.file:0
msgid "Import Details"
msgstr "Details import"
#. module: account_banking
#: field:account.bank.statement.line,period_id:0
msgid "Period"
msgstr "Periode"
#. module: account_banking
#: selection:payment.line,export_state:0
msgid "Done"
msgstr "Verwerkt"
#. module: account_banking
#: view:payment.order:0
msgid "Select Invoices to Pay"
msgstr "Kies te betalen facturen"
#. module: account_banking
#: field:account.banking.imported.file,user_id:0
msgid "Responsible User"
msgstr "Verantwoordelijke gebruiker"
#. module: account_banking
#: code:addons/account_banking/account_banking.py:0
#, python-format
msgid "The statement balance is incorrect !\n"
msgstr "Het saldo op het afschrift is onjuist!\n"
#. module: account_banking
#: constraint:ir.model:0
msgid "The Object name must start with x_ and not contain any special character !"
msgstr "De objectnaam moet beginnen met x_ en mag geen speciale karakters bevatten !"
#. module: account_banking
#: selection:account.banking.imported.file,state:0
msgid "Unfinished"
msgstr "Onvoltooid"
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Statements found for unknown account %(bank_account)s"
msgstr "Afschriften gevonden voor onbekende bankrekening %(bank_account)s"
#. module: account_banking
#: model:ir.module.module,shortdesc:account_banking.module_meta_information
msgid "Account Banking"
msgstr "Account Banking"
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Total number of statements"
msgstr "Totaal aantal afschriften"
#. module: account_banking
#: field:account.banking.imported.file,format:0
#: wizard_field:account_banking.banking_import,init,parser:0
msgid "File Format"
msgstr "Bestandsformaat"
#. module: account_banking
#: field:account.banking.account.settings,journal_id:0
msgid "Journal"
msgstr "Dagboek"
#. module: account_banking
#: selection:account.banking.imported.file,state:0
msgid "Finished"
msgstr "Gereed"
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "No suitable fiscal year found for company %(company_name)s"
msgstr "Geen geschikt boekjaar gevonden voor bedrijf %(company_name)s"
#. module: account_banking
#: view:account.banking.account.settings:0
msgid "Bank Account Details"
msgstr "Details bankrekening"
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Unable to link transaction %(trans)s to invoice: '\n"
" '%(no_candidates)s candidates found; can\'t choose."
msgstr "Niet in staat transactie %(trans)s aan factuur te koppelen: '\n"
" '%(no_candidates)s kandidaten gevonden; kan niet kiezen."
#. module: account_banking
#: field:account.bank.statement.line,partner_bank_id:0
#: field:account.banking.account.settings,partner_bank_id:0
msgid "Bank Account"
msgstr "Bankrekening"
#. module: account_banking
#: code:addons/account_banking/wizard/bank_import.py:0
#, python-format
msgid "Number of transactions loaded"
msgstr "Aantal geladen transacties"
#. module: account_banking
#: code:addons/account_banking/wizard/banktools.py:0
#, python-format
msgid "Multiple overlapping fiscal years found for date %(date)s"
msgstr "Meerdere overlappende boekjaren gevonden voor datum %(date)s"
#. module: account_banking
#: model:ir.model,name:account_banking.model_account_banking_imported_file
msgid "Imported Bank Statements File"
msgstr "Geïmporteerde bankafschrift bestand"

24
parsers/__init__.py Normal file
View File

@@ -0,0 +1,24 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
# All Rights Reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import models
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

41
parsers/convert.py Normal file
View File

@@ -0,0 +1,41 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
# All Rights Reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
__all__ = ['str2date', 'date2str', 'date2date']
import datetime
def str2date(datestr, format='%d/%m/%y'):
'''Convert a string to a datatime object'''
return datetime.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
format
'''
return date2str(str2date(datestr, fromfmt), tofmt)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

171
parsers/models.py Normal file
View File

@@ -0,0 +1,171 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
# All Rights Reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from tools.translate import _
class mem_bank_statement(object):
'''
A mem_bank_statement is a real life projection of a bank statement paper
containing a report of one or more transactions done. As these reports can
contain payments that originate in several accounting periods, period is an
attribute of mem_bank_transaction, not of mem_bank_statement.
Also note that the statement_id is copied from the bank statement, and not
generated from any sequence. This enables us to skip old data in new
statement files.
'''
# Lock attributes to enable parsers to trigger non-conformity faults
__slots__ = [
'start_balance','end_balance', 'date', 'local_account',
'local_currency', 'id', 'statements'
]
def __init__(self, *args, **kwargs):
super(mem_bank_statement, self).__init__(*args, **kwargs)
self.id = ''
self.local_account = ''
self.local_currency = ''
self.start_balance = 0.0
self.end_balance = 0.0
self.date = ''
self.transactions = []
def is_valid(self):
'''
Final check: ok if calculated end_balance and parsed end_balance are
identical and perform a heuristic check on the transactions.
'''
if any([x for x in self.transactions if not x.is_valid()]):
return False
check = float(self.start_balance)
for transaction in self.transactions:
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
OpenERP moves and linking to invoices and the like is done afterwards.
'''
# Lock attributes to enable parsers to trigger non-conformity faults
__slots__ = [
'id', 'local_account', 'local_currency', 'execution_date',
'effective_date', 'remote_owner', 'remote_account',
'remote_currency', 'transferred_amount', 'transfer_type',
'reference', 'message', 'statement_id',
]
def __init__(self, *args, **kwargs):
super(mem_bank_transaction, self).__init__(*args, **kwargs)
self.id = ''
self.local_account = ''
self.local_currency = ''
self.execution_date = ''
self.effective_date = ''
self.remote_account = ''
self.remote_owner = ''
self.remote_currency = ''
self.transferred_amount = ''
self.transfer_type = ''
self.reference = ''
self.message = ''
self.statement_id = ''
def is_valid(self):
'''
Heuristic check: at least id, execution_date, remote_account and
transferred_amount should be filled to create a valid transfer.
'''
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.
'''
parsers = []
parser_by_name = {}
parser_by_code = {}
parser_by_classname = {}
def __new__(metacls, clsname, bases, clsdict):
newcls = type.__new__(metacls, clsname, bases, clsdict)
if 'name' in clsdict and newcls.name:
metacls.parsers.append(newcls)
metacls.parser_by_name[newcls.name] = newcls
metacls.parser_by_code[newcls.code] = newcls
metacls.parser_by_classname[clsname] = newcls
return newcls
@classmethod
def get_parser_types(cls, sort='name'):
'''
Return the parser class names, optional in sort order.
'''
if sort == 'name':
keys = cls.parser_by_name.keys()
parsers = cls.parser_by_name
else:
keys = cls.parser_by_code.itervalues()
parsers = cls.parser_by_code
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
it to implement your own.
You should at least implement the following at the class level:
name -> the name of the parser, shown to the user and
translatable.
code -> the identifier you care to give it. Not translatable
doc -> the description of the identifier. Shown to the user.
Translatable.
parse -> the method for the actual parsing.
'''
__metaclass__ = parser_type
name = None
code = None
doc = __doc__
def parse(self, 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
should contain a list of mem_bank_transaction objects.
For identification purposes, don't invent numbering of the transaction
numbers or bank statements ids on your own - stick with those provided
by your bank. Doing so enables the users to re-load old transaction
files without creating multiple identical bank statements.
'''
raise NotImplementedError(
_('This is a stub. Please implement your own.')
)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

182
record.py Normal file
View File

@@ -0,0 +1,182 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
# All Rights Reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
__all__ = [
'Field', 'Filler', 'DateField', 'NumberField', 'RightAlignedField',
'RecordType', 'Record', 'asciify'
]
__doc__ = '''Ease working with fixed length records in files'''
from datetime import datetime, date
import unicodedata
class Field(object):
'''Base Field class - fixed length left aligned string field in a record'''
def __init__(self, name, length=1, fillchar=' '):
self.name = name.replace(' ', '_')
self.length = length
self.fillchar = fillchar
def format(self, value):
value = str(value)
if len(value) > self.length:
return value[len(value) - self.length:]
return value.ljust(self.length, self.fillchar)
def take(self, buffer):
offset = hasattr(self, 'offset') and self.offset or 0
return 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=' '):
super(Filler, self).__init__(name, length)
self.value = str(value)
def take(self, buffer):
return self.format(buffer)
def format(self, value):
return super(Filler, self).format(
self.value * (self.length / len(self.value) +1)
)
class DateField(Field):
'''Variable date field'''
def __init__(self, name, format='%Y-%m-%d', auto=False):
length = len(date.today().strftime(format))
super(DateField, self).__init__(name, length)
self.dateformat = format
self.auto = auto
def format(self, value):
if isinstance(value, (str, unicode)) and \
len(value.strip()) == self.length:
value = datetime.strptime(value, self.dateformat).date()
elif not isinstance(value, (datetime, date)):
value = date.today()
return value.strftime(self.dateformat)
def take(self, buffer):
value = super(DateField, self).take(buffer)
if value:
return datetime.strptime(value, self.dateformat).date()
return self.auto and date.today() or None
class RightAlignedField(Field):
'''Deviation of Field: right aligned'''
def format(self, value):
if len(value) > self.length:
return value[:self.length]
return value.rjust(self.length, self.fillchar)
def take(self, buffer):
offset = hasattr(self, 'offset') and self.offset or 0
return buffer[offset:offset + self.length].lstrip(self.fillchar)
class NumberField(RightAlignedField):
'''Deviation of Field: left zero filled'''
def __init__(self, *args, **kwargs):
kwargs['fillchar'] = '0'
super(NumberField, self).__init__(*args, **kwargs)
def format(self, value):
return super(NumberField, self).format(value and str(value) or '')
class RecordType(object):
fields = []
def __init__(self, fields=[]):
if fields:
self.fields = fields
offset = 0
for field in self.fields:
field.offset = offset
offset += field.length
def __len__(self):
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)
def __getitem__(self, key):
for field in self.fields:
if field.name == key:
return field
raise KeyError, 'No such field: %s' % key
def format(self, buffer):
result = []
for field in self.fields:
result.append(field.format(field.take(buffer)))
return ''.join(result)
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
def __init__(self, recordtype=None, value=''):
if hasattr(self, '_fields') and self._fields:
self._recordtype = RecordType(self._fields)
if not self._recordtype and not recordtype:
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
def __setattr__(self, attr, value):
if attr.startswith('_'):
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:]
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(str(self))
def asciify(str):
return unicodedata.normalize('NFKD', str).encode('ascii', 'ignore')
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

436
sepa.py Normal file
View File

@@ -0,0 +1,436 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
# All Rights Reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
# The information about SEPA account numbers in this module are collected
# from ISO 13616-1, which can be found at SWIFT's website:
# http://www.swift.com/solutions/messaging/information_products/bic_downloads_documents/pdfs/IBAN_Registry.pdf
#
# This module uses both SEPA and IBAN as seemingly interchangeble terms.
# However, a SEPA account is a bank account in the SEPA zone, which is
# represented by a IBAN number, which is build up from a ISO-693-1 two letter
# country code, two check digits and a BBAN number, representing the
# local/national accounting scheme.
#
# With the exception of Turkey, all countries use the full local adressing
# scheme in the IBAN numbers, making it possible to deduce the BBAN from the
# IBAN. As Turkey uses an additional code in the local scheme which is not
# part of the BBAN, for accounts located in Turkeys banks it is not possible
# to use the BBAN to reconstruct the local account.
#
# WARNING:
# This module contains seemingly enough info to create IBAN's from BBAN's.
# Although many BBAN/IBAN conversions seem algorithmic, there is enough
# deviation to take the warning from SEPA seriously: this is the domain of the
# account owning banks. Don't use it, unless you are prepared to loose your
# money. It is for heuristic validation purposes only.
__all__ = ['IBAN']
def modulo_97_base10(abuffer):
'''
Calculate the modulo 97 value of a string in base10
'''
checksum = int(abuffer[0])
for digit in abuffer[1:]:
checksum *= 10
checksum += int(digit)
checksum %= 97
return checksum
def base36_to_base10str(abuffer):
'''
Convert a base36 string value to a string of base10 digits.
'''
result = ''
for digit in abuffer:
if digit.isalpha():
result += str(ord(digit) - 55)
else:
result += digit
return result
class BBANFormat(object):
'''
A BBANFormat is an auxilliary class for IBAN. It represents the composition
of a BBAN number from the different elements in order to translate a
IBAN number to a localized number. The reverse route, transforming a local
account to a SEPA account, is the sole responsibility of the banks.
'''
def __init__(self, ibanfmt, bbanfmt='%A', nolz=False):
'''
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
B = Bank code digit
C = Branch code digit
V = Account check digit
W = Bank code check digit
X = Additional check digit (some countries check everything)
P = Account prefix digit
The combination of N and A can be used to encode minimum length
leading-zero-stripped account numbers.
Example: (NL) 'CCCCAAAAAAAAAA'
will convert 'INGB0001234567' into
bankcode 'INGB' and account '0001234567'
bbanfmt: string of placeholders for the local bank account
%C: bank code
%B: branch code
%I: IBAN number (complete)
%T: account type
%P: account prefix
%A: account number. This will include the 'N' placeholder
positions in the ibanfmt.
%V, %W, %X: check digits (separate meanings)
%Z: IBAN check digits (only Poland uses these)
%%: %
anything else: literal copy
Example: (AT): '%A BLZ %C'
nolz: boolean indicating stripping of leading zeroes in the account
number. Defaults to False
'''
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)
if i < 0:
return ''
result = ''
j = len(self._iban)
while i < j and self._iban[i] == spec:
result += value[i+4]
i += 1
return self._nolz and result.lstrip('0') or result
def bankcode(self, iban):
'''Return the bankcode'''
return self.__extract__('B', iban)
def branchcode(self, iban):
'''Return the branch code'''
return self.__extract__('C', iban)
def account(self, iban):
'''Return the account number'''
if self._iban.find('N') >= 0:
prefix = self.__extract__('N', iban).lstrip('0')
else:
prefix = ''
return prefix + self.__extract__('A', iban)
def BBAN(self, iban):
'''
Format the BBAN part of the IBAN in iban following the local
addressing scheme. We need the full IBAN in order to be able to use
the IBAN check digits in it, as Poland needs.
'''
res = ''
i = 0
while i < len(self._bban):
if self._bban[i] == '%':
i += 1
parm = self._bban[i]
if parm in 'BCDPTVWX':
res += self.__extract__(parm, iban)
elif parm == 'A':
res += self.account(iban)
elif parm == 'S':
res += iban
elif parm == 'Z':
# IBAN check digits (Poland)
res += iban[2:4]
elif parm == '%':
res += '%'
else:
res += self._bban[i]
i += 1
return res
class IBAN(str):
'''
A IBAN string represents a SEPA bank account number. This class provides
the interpretation and some validation of such strings.
Mind that, although there is sufficient reason to comment on the chosen
approach, we are talking about a transition period of at max. 1 year. Good
is good enough.
'''
BBAN_formats = {
'AL': BBANFormat('CCBBBBVAAAAAAAAAAAAAAAAAA', '%B%A'),
'AD': BBANFormat('CCCCBBBBAAAAAAAAAAAA', '%A'),
'AT': BBANFormat('BBBBBAAAAAAAAAAA', '%A BLZ %C'),
'BE': BBANFormat('CCCAAAAAAAVV', '%C-%A-%V'),
'BA': BBANFormat('BBBCCCAAAAAAAA', '%I'),
'BG': BBANFormat('BBBBCCCCAAAAAAAAAA', '%I'),
'CH': BBANFormat('CCCCCAAAAAAAAAAAAV', '%C %A', nolz=True),
'CS': BBANFormat('BBBAAAAAAAAAAAAAVV', '%B-%A-%V'),
'CY': BBANFormat('BBBCCCCCAAAAAAAAAAAAAAAA', '%B%C%A'),
'CZ': BBANFormat('BBBBPPPPPPAAAAAAAAAA', '%B-%P/%A'),
'DE': BBANFormat('BBBBBBBBAAAAAAAAAAV', '%A%V BLZ %B'),
'DK': BBANFormat('CCCCAAAAAAAAAV', '%C %A%V'),
'EE': BBANFormat('BBCCAAAAAAAAAAAV', '%A%V'),
'ES': BBANFormat('BBBBCCCCWVAAAAAAAAAA', '%B%C%W%V%A'),
'FI': BBANFormat('CCCCTTAAAAAAAV', '%C-%A%V', nolz=True),
'FR': BBANFormat('BBBBBCCCCCAAAAAAAAAAAVV', '%B %C %A %V'),
'FO': BBANFormat('BBBBAAAAAAAAAV', '%B %A%V'),
# Great Brittain uses a special display for the branch code, which we
# can't honor using the current system. If this appears to be a
# problem, we can come up with something later.
'GB': BBANFormat('BBBBCCCCCCAAAAAAAAV', '%C %A'),
'GI': BBANFormat('BBBBAAAAAAAAAAAAAAA', '%A'),
'GL': BBANFormat('CCCCAAAAAAAAAV', '%C %A%V'),
'GR': BBANFormat('BBBCCCCAAAAAAAAAAAAAAAA', '%B-%C-%A', nolz=True),
'HR': BBANFormat('BBBBBBBAAAAAAAAAA', '%B-%A'),
'HU': BBANFormat('BBBCCCCXAAAAAAAAAAAAAAAV', '%B%C%X %A%V'),
'IE': BBANFormat('BBBBCCCCCCAAAAAAAAV', '%C %A%V'),
'IL': BBANFormat('BBBCCCAAAAAAAAAAAAA', '%C%A'),
# Iceland uses an extra identification number, split in two on
# display. Coded here as %P%V.
'IS': BBANFormat('CCCCTTAAAAAAPPPPPPVVVV', '%C-%T-%A-%P-%V'),
'IT': BBANFormat('WBBBBBCCCCCCAAAAAAAAAAAAV', '%W/%B/%C/%A%V'),
'LV': BBANFormat('BBBBAAAAAAAAAAAAA', '%I'),
'LI': BBANFormat('CCCCCAAAAAAAAAAAA', '%C %A', nolz=True),
'LT': BBANFormat('BBBBBAAAAAAAAAAA', '%I'),
'LU': BBANFormat('BBBAAAAAAAAAAAAA', '%I'),
'MC': BBANFormat('BBBBBCCCCCAAAAAAAAAAAVV', '%B %C %A %V'),
'ME': BBANFormat('CCCAAAAAAAAAAAAAVV', '%C-%A-%V'),
'MK': BBANFormat('BBBAAAAAAAAAAVV', '%B-%A-%V', nolz=True),
'MT': BBANFormat('BBBBCCCCCAAAAAAAAAAAAAAAAAA', '%A', nolz=True),
# Mauritius has an aditional bank identifier, a reserved part and the
# currency as part of the IBAN encoding. As there is no representation
# given for the local account in ISO 13616-1 we assume IBAN, which
# circumvents the BBAN display problem.
'MU': BBANFormat('BBBBBBCCAAAAAAAAAAAAVVVWWW', '%I'),
# Netherlands has two different local account schemes: one with and
# one without check digit (9-scheme and 7-scheme). Luckily most Dutch
# financial services can keep the two apart without telling, so leave
# that. Also leave the leading zero issue, as most banks are already
# converting their local account numbers to BBAN format.
'NL': BBANFormat('BBBBAAAAAAAAAA', '%A'),
# Norway seems to split the account number in two on display. For now
# we leave that. If this appears to be a problem, we can fix it later.
'NO': BBANFormat('CCCCAAAAAV', '%C.%A%V'),
'PL': BBANFormat('CCCCCCCCAAAAAAAAAAAAAAAA', '%Z%C %A'),
'PT': BBANFormat('BBBBCCCCAAAAAAAAAAAVV', '%B.%C.%A.%V'),
'RO': BBANFormat('BBBBAAAAAAAAAAAAAAAA', '%A'),
'SA': BBANFormat('BBAAAAAAAAAAAAAAAA', '%B%A'),
'SE': BBANFormat('CCCAAAAAAAAAAAAAAAAV', '%A'),
'SI': BBANFormat('CCCCCAAAAAAAAVV', '%C-%A%V', ),
# Slovakia uses two different format for local display. We stick with
# their formal BBAN specs
'SK': BBANFormat('BBBBPPPPPPAAAAAAAAAAAA', '%B%P%A'),
# San Marino: No information for display of BBAN, so stick with IBAN
'SM': BBANFormat('WBBBBBCCCCCCAAAAAAAAAAAAV', '%I'),
'TN': BBANFormat('BBCCCAAAAAAAAAAAAAVV', '%B %C %A %V'),
# Turkey has insufficient information in the IBAN number to regenerate
# the BBAN: the branch code for local addressing is missing (5n).
'TR': BBANFormat('BBBBBWAAAAAAAAAAAAAAAA', '%B%C%A'),
}
countries = BBAN_formats.keys()
unknown_BBAN_format = BBANFormat('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', '%I')
def __new__(cls, arg, **kwargs):
'''
All letters should be uppercase and acceptable. As str is an
in 'C' implemented class, this can't be done in __init__.
'''
init = ''
for item in arg.upper():
if item.isalnum():
init += item
elif item not in ' \t.-':
raise ValueError, 'Invalid chars found in IBAN number'
return str.__new__(cls, init)
def __init__(self, *args, **kwargs):
'''
Sanity check: don't offer extensions unless the base is sound.
'''
super(IBAN, self).__init__()
# Rearrange position of country code and check digits
if self.countrycode not in self.countries:
self.BBAN_format = self.unknown_BBAN_format
raise Warning, \
'Don\'t know how to format BBAN for country %s' % \
self.countrycode
else:
self.BBAN_format = self.BBAN_formats[self.countrycode]
@classmethod
def create(cls, BIC=None, countrycode=None, BBAN=None, bankcode=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.
Incomplete: can only work with valid BBAN now.
'''
if BIC:
if not bankcode:
bankcode = BIC[:4]
if not countrycode:
countrycode = BIC[4:6]
else:
if countrycode:
countrycode = countrycode.upper()
else:
raise ValueError, \
'Either BIC or countrycode is required'
if countrycode not in cls.countries:
raise ValueError, \
'%s is not a SEPA country' % countrycode
format = cls.BBAN_formats[countrycode]
if BBAN:
if len(BBAN) == len(formats._iban):
ibanno = cls(countrycode + '00' + BBAN)
return cls(countrycode + ibanno.checksum + BBAN)
raise ValueError, \
'Insufficient data to generate IBAN'
@property
def valid(self):
'''
Check if the string + check digits deliver a valid checksum
'''
_buffer = self[4:] + self[:4]
return int(base36_to_base10str(_buffer)) % 97 == 1
def __repr__(self):
'''
Formal representation is in chops of four characters, devided by a
space.
'''
parts = []
for i in range(0, len(self), 4):
parts.append(self[i:i+4])
return ' '.join(parts)
def __unicode__(self):
'''
Return unicode representation of self
'''
return u'%r' % self
@property
def checksum(self):
'''
Generate a new checksum for an otherwise correct layed out BBAN in a
IBAN string.
NOTE: This is the responsability of the banks. No guaranties whatsoever
that this delivers usable IBAN accounts. Mind your money!
'''
_buffer = self[4:] + self[:2] + '00'
_buffer = base36_to_base10str(_buffer)
return '%.2d' % (98 - modulo_97_base10(_buffer))
@property
def checkdigits(self):
'''
Return the digits which form the checksum in the IBAN string
'''
return self[2:4]
@property
def countrycode(self):
'''
Return the ISO country code
'''
return self[:2]
@property
def bankcode(self):
'''
Return the bank code
'''
return self.BBAN_format.bankcode(self)
@property
def BIC_searchkey(self):
'''
BIC's, or Bank Identification Numbers, are composed of the bank
code, followed by the country code, followed by the localization
code, followed by an optional department number.
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
the full BIC.
'''
return self.bankcode[:4] + self.countrycode
@property
def branchcode(self):
'''
Return the branch code
'''
return self.BBAN_format.branchcode(self)
@property
def localized_BBAN(self):
'''
Localized format of local or Basic Bank Account Number, aka BBAN
'''
if self.countrycode == 'TR':
raise NotImplementedError, (
'The Turkish BBAN requires information that is not in the '
'IBAN number.'
)
return self.BBAN_format.BBAN(self)
@property
def BBAN(self):
'''
Return full encoded BBAN, which is for all countries the IBAN string
after the ISO-639 code and the two check digits.
'''
return self[4:]
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

23
wizard/__init__.py Normal file
View File

@@ -0,0 +1,23 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
# All Rights Reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import bank_import
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

589
wizard/bank_import.py Normal file
View File

@@ -0,0 +1,589 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
# All Rights Reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
'''
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 pooler
import time
import wizard
import base64
from tools import config
from tools.translate import _
from account_banking.parsers import models
from account_banking.parsers.convert import *
from banktools import *
def _get_move_info(pool, cursor, uid, move_line):
reconcile_obj = pool.get('account.bank.statement.reconcile')
type_map = {
'out_invoice': 'customer',
'in_invoice': 'supplier',
'out_refund': 'customer',
'in_refund': 'supplier',
}
retval = struct(move_line=move_line)
retval.reference = move_line.ref
if move_line.invoice:
retval.invoice = move_line.invoice
retval.type = type_map[move_line.invoice.type]
else:
retval.type = 'general'
move_line.reconcile_id = reconcile_obj.create(
cursor, uid, {'line_ids': [(6, 0, [move_line.id])]}
)
return retval
def _link_payment(pool, cursor, uid, trans, payment_lines,
partner_id, bank_account_id, log):
'''
Find the payment order belonging to this reference - if there is one
This is the easiest part: when sending payments, the returned bank info
should be identical to ours.
'''
# TODO: Not sure what side effects are created when payments are done
# for credited customer invoices, which will be matched later on too.
digits = int(config['price_accuracy'])
candidates = [x for x in payment_lines
if x.communication == trans.reference
and round(x.amount, digits) == -round(trans.transferred_amount, digits)
and trans.remote_account in (x.bank_id.acc_number,
x.bank_id.iban)
]
if len(candidates) == 1:
candidate = candidates[0]
payment_line_obj = pool.get('payment.line')
payment_line_obj.write(cursor, uid, [candidate.id], {
'export_state': 'done',
'date_done': trans.effective_date.strftime('%Y-%m-%d')}
)
return _get_move_info(pool, cursor, uid, candidate.move_line_id)
return False
def _link_invoice(pool, cursor, uid, trans, move_lines,
partner_id, bank_account_id, log):
'''
Find the invoice belonging to this reference - if there is one
Use the sales journal to check.
Challenges we're facing:
1. The sending or receiving party is not necessarily the same as the
partner the payment relates to.
2. References can be messed up during manual encoding and inexact
matching can link the wrong invoices.
3. Amounts can or can not match the expected amount.
4. Multiple invoices can be paid in one transaction.
.. There are countless more, but these we'll try to address.
Assumptions for matching:
1. There are no payments for invoices not sent. These are dealt with
later on.
1. Debit amounts are either customer invoices or credited supplier
invoices.
2. Credit amounts are either supplier invoices or credited customer
invoices.
3. Payments are either below expected amount or only slightly above
(abs).
4. Payments from partners that are matched, pay their own invoices.
Worst case scenario:
1. No match was made.
No harm done. Proceed with manual matching as usual.
2. The wrong match was made.
Statements are encoded in draft. You will have the opportunity to
manually correct the wrong assumptions.
'''
# First on partner
candidates = [x for x in move_lines if x.partner_id.id == partner_id]
# Next on reference/invoice number. Mind that this uses the invoice
# itself, as the move_line references have been fiddled with on invoice
# creation. This also enables us to search for the invoice number in the
# reference instead of the other way around, as most human interventions
# *add* text.
if not candidates:
ref = trans.reference.upper()
msg = trans.message.upper()
candidates = [x for x in move_lines
if x.invoice.number.upper() in ref or
x.invoice.number.upper() in msg
]
if len(candidates) > 1:
# TODO: currency coercing
digits = int(config['price_accuracy'])
if trans.transferred_amount < 0:
func = lambda x, y=abs(trans.transferred_amount), z=digits:\
round(x.debit, z) == round(y, z)
else:
func = lambda x, y=abs(trans.transferred_amount), z=digits:\
round(x.credit, z) == round(y, z)
best = [x for x in move_lines if func(x)]
if len(best) != 1:
log.append(
_('Unable to link transaction %(trans)s to invoice: '
'%(no_candidates)s candidates found; can\'t choose.') % {
'trans': trans.id,
'no_candidates': len(best)
})
return False
if len(candidates) == 1:
return _get_move_info(pool, cursor, uid, candidates[0])
return False
def _link_canceled_debit(pool, cursor, uid, trans, payment_lines,
partner_id, bank_account_id, log):
'''
Direct debit transfers can be canceled by the remote owner within a
legaly defined time period. These 'payments' are most likely
already marked 'done', which makes them harder to match. Also the
reconciliation has to be reversed.
'''
# TODO: code _link_canceled_debit
return False
def _banking_import_statements_file(self, cursor, uid, data, context):
'''
Import bank statements / bank transactions file.
This module/function represents the business logic, the parser modules
represent the decoding logic.
'''
form = data['form']
statements_file = form['file']
data = base64.decodestring(statements_file)
pool = pooler.get_pool(cursor.dbname)
company_obj = pool.get('res.company')
user_obj = pool.get('res.user')
journal_obj = pool.get('account.journal')
move_line_obj = pool.get('account.move.line')
payment_line_obj = pool.get('payment.line')
statement_obj = pool.get('account.bank.statement')
statement_line_obj = pool.get('account.bank.statement.line')
statement_file_obj = pool.get('account.banking.imported.file')
#account_obj = pool.get('account.account')
#payment_order_obj = pool.get('payment.order')
#currency_obj = pool.get('res.currency')
# get the parser to parse the file
parser_code = form['parser']
parser = models.create_parser(parser_code)
if not parser:
raise wizard.except_wizard(
_('ERROR!'),
_('Unable to import parser %(parser)s. Parser class not found.') %
{'parser':parser_code}
)
# Get the company
company = form['company']
if not company:
user_data = user_obj.browse(cursor, uid, uid, context)
company = company_obj.browse(
cursor, uid, company or user_data.company_id.id, context
)
# Parse the file
statements = parser.parse(data)
if any([x for x in statements if not x.is_valid()]):
raise wizard.except_wizard(
_('ERROR!'),
_('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(cursor, uid, dict(
company_id = company.id,
file = statements_file,
date = time.strftime('%Y-%m-%d'),
user_id = uid,
state = 'unfinished'
))
# Results
no_stat_loaded = 0
no_trans_loaded = 0
no_stat_skipped = 0
no_trans_skipped = 0
no_trans_matched = 0
no_errors = 0
log = []
# Caching
error_accounts = {}
info = {}
imported_statement_ids = []
if statements:
# Get interesting journals once
if company:
journal_ids = journal_obj.search(cursor, uid, [
('type', 'in', ('sale','purchase')),
('company_id', '=', company.id),
])
else:
journal_ids = None
if not journal_ids:
journal_ids = journal_obj.search(cursor, uid, [
('type', 'in', ('sale','purchase')),
('active', '=', True),
('company_id', '=', False),
])
# Get all unreconciled moves predating the last statement in one big
# swoop. Assumption: the statements in the file are sorted in ascending
# order of date.
move_line_ids = move_line_obj.search(cursor, uid, [
('reconcile_id', '=', False),
('journal_id', 'in', journal_ids),
('account_id.reconcile', '=', True),
('date', '<=', date2str(statements[-1].date)),
])
move_lines = move_line_obj.browse(cursor, uid, move_line_ids)
# Get all unreconciled sent payment lines in one big swoop.
# No filtering can be done, as empty dates carry value for C2B
# communication. Most likely there are much less sent payments
# than reconciled and open/draft payments.
cursor.execute("SELECT l.id FROM payment_order o, payment_line l "
"WHERE l.order_id = o.id AND "
"o.state = 'sent' AND "
"l.date_done IS NULL"
)
payment_line_ids = [x[0] for x in cursor.fetchall()]
if payment_line_ids:
payment_lines = payment_line_obj.browse(cursor, uid, payment_line_ids)
else:
payment_lines = []
for statement in statements:
if statement.local_account in error_accounts:
# Don't repeat messages
no_stat_skipped += 1
no_trans_skipped += len(statement.transactions)
continue
if not statement.local_account in info:
account_info = get_company_bank_account(
pool, cursor, uid, statement.local_account, company, log
)
if not account_info:
log.append(
_('Statements found for unknown account %(bank_account)s') %
{'bank_account': statement.local_account}
)
error_accounts[statement.local_account] = True
no_errors += 1
continue
if 'journal_id' not in account_info:
log.append(
_('Statements found for account %(bank_account)s, '
'but no default journal was defined.'
) % {'bank_account': statement.local_account}
)
error_accounts[statement.local_account] = True
no_errors += 1
continue
info[statement.local_account] = account_info
else:
account_info = info[statement.local_account]
if statement.local_currency \
and account_info.journal_id.code != statement.local_currency:
# TODO: convert currencies?
log.append(
_('Statement for account %(bank_account)s uses different '
'currency than the defined bank journal.') %
{'bank_account': statement.local_account}
)
error_accounts[statement.local_account] = True
no_errors += 1
continue
# Check existence of previous statement
statement_ids = statement_obj.search(cursor, uid, [
('name', '=', statement.id),
('date', '=', date2str(statement.date)),
])
if statement_ids:
log.append(
_('Statement %(id)s known - skipped') % {
'id': statement.id
}
)
continue
statement_id = statement_obj.create(cursor, uid, dict(
name = statement.id,
journal_id = account_info.journal_id.id,
date = date2str(statement.date),
balance_start = statement.start_balance,
balance_end_real = statement.end_balance,
balance_end = statement.end_balance,
state = 'draft',
currency = account_info.journal_id.currency.id,
user_id = uid,
banking_id = import_id,
))
imported_statement_ids.append(statement_id)
# move each line to the right period and try to match it with an
# invoice or payment
subno = 0
for transaction in statement.transactions:
move_info = False
# Keep a tracer for identification of order in a statement in case
# of missing transaction ids.
subno += 1
# Link remote partner, import account when needed
partner_bank = get_bank_account(
pool, cursor, uid, transaction.remote_account, log, fail=True
)
if partner_bank:
partner_id = partner_bank.partner_id.id
partner_bank_id = partner_bank.id
elif transaction.remote_owner:
partner_id = get_or_create_partner(
pool, cursor, uid, transaction.remote_owner
)
if transaction.remote_account:
partner_bank_id = create_bank_account(
pool, cursor, uid, partner_id,
transaction.remote_account, transaction.remote_owner,
log
)
else:
partner_id = False
# Link accounting period
period_id = get_period(pool, cursor, uid,
transaction.effective_date, company,
log)
# Credit means payment... isn't it?
if transaction.transferred_amount < 0 and payment_lines:
# Link open payment - if any
move_info = _link_payment(pool, cursor, uid, transaction,
payment_lines, partner_id,
partner_bank_id, log
)
# Second guess, invoice
if not move_info:
# Link invoice - if any
move_info = _link_invoice(pool, cursor, uid, transaction,
move_lines, partner_id, partner_bank_id,
log
)
if not move_info:
if transaction.transferred_amount < 0:
account_id = account_info.default_credit_account_id
else:
account_id = account_info.default_debit_account_id
else:
account_id = move_info.move_line.account_id
no_trans_matched += 1
values = struct(
name = '%s.%s' % (statement.id, transaction.id or subno),
date = transaction.effective_date,
amount = transaction.transferred_amount,
account_id = account_id.id,
statement_id = statement_id,
note = transaction.message,
ref = transaction.reference,
period_id = period_id,
)
if partner_id:
values.partner_id = partner_id
if partner_bank_id:
values.partner_bank_id = partner_bank_id
if move_info:
values.type = move_info.type
values.reconcile_id = move_info.move_line.reconcile_id
statement_line_id = statement_line_obj.create(cursor, uid, values)
no_trans_loaded += 1
no_stat_loaded += 1
if payment_lines:
# As payments lines are treated as individual transactions, the
# batch as a whole is only marked as 'done' when all payment lines
# have been reconciled.
cursor.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 o.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'), no_stat_skipped + no_stat_loaded),
'%s: %s' % (_('Total number of transactions'), no_trans_skipped + no_trans_loaded),
'%s: %s' % (_('Number of errors found'), no_errors),
'%s: %s' % (_('Number of statements skipped due to errors'), no_stat_skipped),
'%s: %s' % (_('Number of transactions skipped due to errors'), no_trans_skipped),
'%s: %s' % (_('Number of statements loaded'), no_stat_loaded),
'%s: %s' % (_('Number of transactions loaded'), no_trans_loaded),
'',
'%s:' % ('Error report'),
'',
]
text_log = '\n'.join(report + log)
state = no_errors and 'error' or 'ready'
statement_file_obj.write(cursor, uid, import_id, dict(
state = state, log = text_log,
))
return dict(
log = text_log,
statement_ids = imported_statement_ids
)
banking_import_form = '''<?xml version="1.0"?>
<form string="Import Bank Transactions File">
<separator colspan="4" string="Select the processing details:" />
<field name="company" colspan="1" />
<field name="file"/>
<newline />
<field name="parser"/>
</form>
'''
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
'''
return models.parser_type.get_parser_types()
banking_import_fields = dict(
company = dict(
string = 'Company',
type = 'many2one',
relation = 'res.company',
required = True,
),
file = dict(
string = 'Statements File',
type = 'binary',
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.')
),
parser = dict(
string = 'File Format',
type = 'selection',
selection = parser_types,
required = True,
),
)
result_form = '''<?xml version="1.0"?>
<form string="Import Bank Transactions File">
<separator colspan="4" string="Results:" />
<field name="log" colspan="4" nolabel="1" width="500"/>
</form>
'''
result_fields = dict(
log = dict(string='Log', type='text')
)
class banking_import(wizard.interface):
'''
Wizard to import bank statements. Generic code, parsing is done in the
parser modules.
'''
def _action_open_window(self, cursor, uid, data, context):
'''
Open a window with the resulting bank statements
'''
# TODO: this needs fiddling. The resulting window is informative,
# but not very usefull...
form = data['form']
return dict(
domain = "[('id','in',(%s,))]" % (','.join(map(str, form['statement_ids']))),
name = 'Statement',
view_type = 'tree',
view_mode = 'form,tree',
res_model = 'account.bank.statement',
view_id = False,
type = 'ir.actions.act_window',
res_id = form['statement_ids'],
)
states = {
'init' : {
'actions' : [],
'result' : {
'type' : 'form',
'arch' : banking_import_form,
'fields': banking_import_fields,
'state': [('end', '_Cancel', 'gtk-cancel'),
('import', '_Ok', 'gtk-ok'),
]
}
},
'import' : {
'actions': [_banking_import_statements_file],
'result': {
'type': 'form',
'arch': result_form,
'fields': result_fields,
'state': [('end', '_Close', 'gtk-close'),
('open', '_Open Statement', 'gtk-ok'),
]
}
},
'open': {
'actions': [],
'result': {
'type': 'action',
'action': _action_open_window,
'state': 'end'
}
},
}
banking_import('account_banking.banking_import')
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

294
wizard/banktools.py Normal file
View File

@@ -0,0 +1,294 @@
# -*- encoding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
# All Rights Reserved
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from tools.translate import _
__all__ = [
'get_period',
'get_bank_account',
'get_or_create_partner',
'get_company_bank_account',
'create_bank_account',
'struct',
]
class struct(dict):
'''
Ease working with dicts. Allow dict.key alongside dict['key']
'''
def __setattr__(self, item, value):
self.__setitem__(item, value)
def __getattr__(self, item):
return self.__getitem__(item)
def show(self, indent=0, align=False, ralign=False):
'''
PrettyPrint method. Aligns keys right (ralign) or left (align).
'''
if align or ralign:
width = 0
for key in self.iterkeys():
width = max(width, len(key))
alignment = ''
if not ralign:
alignment = '-'
fmt = '%*.*s%%%s%d.%ds: %%s' % (
indent, indent, '', alignment, width, width
)
else:
fmt = '%*.*s%%s: %%s' % (indent, indent, '')
for item in self.iteritems():
print fmt % item
import datetime
from account_banking import sepa
from account_banking.parsers.convert import *
def get_period(pool, cursor, uid, date, company, log):
'''
Get a suitable period for the given date range and the given company.
'''
fiscalyear_obj = pool.get('account.fiscalyear')
period_obj = pool.get('account.period')
if not date:
date = date2str(datetime.datetime.today())
search_date = date2str(date)
fiscalyear_ids = fiscalyear_obj.search(cursor, uid, [
('date_start','<=', search_date), ('date_stop','>=', search_date),
('state','=','draft'), ('company_id','=',company.id)
])
if not fiscalyear_ids:
fiscalyear_ids = fiscalyear_obj.search(cursor, uid, [
('date_start','<=',search_date), ('date_stop','>=',search_date),
('state','=','draft'), ('company_id','=',None)
])
if not fiscalyear_ids:
log.append(
_('No suitable fiscal year found for company %(company_name)s')
% dict(company_name=company.name)
)
return False
elif len(fiscalyear_ids) > 1:
log.append(
_('Multiple overlapping fiscal years found for date %(date)s')
% dict(date=date)
)
return False
fiscalyear_id = fiscalyear_ids[0]
period_ids = period_obj.search(cursor, uid, [
('date_start','<=',search_date), ('date_stop','>=',search_date),
('fiscalyear_id','=',fiscalyear_id), ('state','=','draft')
])
if not period_ids:
log.append(_('No suitable period found for date %(date)s')
% dict(date=date)
)
return False
if len(period_ids) != 1:
log.append(_('Multiple overlapping periods for date %(date)s')
% dict(date=date)
)
return False
return period_ids[0]
def get_bank_account(pool, cursor, uid, account_number, log, fail=False):
'''
Get the bank account with account number account_number
'''
# No need to search for nothing
if not account_number:
return False
partner_bank_obj = pool.get('res.partner.bank')
bank_account_ids = partner_bank_obj.search(cursor, uid, [
('acc_number', '=', account_number)
])
if not bank_account_ids:
bank_account_ids = partner_bank_obj.search(cursor, uid, [
('iban', '=', account_number)
])
if not bank_account_ids:
if not fail:
log.append(
_('Bank account %(account_no)s was not found in the database')
% dict(account_no=account_number)
)
return False
elif len(bank_account_ids) != 1:
log.append(
_('More than one bank account was found with the same number %(account_no)s')
% dict(account_no=account_number)
)
return False
return partner_bank_obj.browse(cursor, uid, bank_account_ids)[0]
def get_or_create_partner(pool, cursor, uid, name, log):
'''
Get or create the partner belonging to the account holders name <name>
'''
partner_obj = pool.get('res.partner')
partner_ids = partner_obj.search(cursor, uid, [('name', 'ilike', name)])
if not partner_ids:
partner_id = partner_obj.create(cursor, uid, dict(
name=name, active=True, comment='Generated by Import Bank Statements File',
))
elif len(partner_ids) > 1:
log.append(
_('More then one possible match found for partner with name %(name)s')
% {'name': name}
)
return False
else:
partner_id = partner_ids[0]
return partner_obj.browse(cursor, uid, partner_id)[0]
def get_company_bank_account(pool, cursor, uid, account_number,
company, log):
'''
Get the matching bank account for this company.
'''
results = struct()
bank_account = get_bank_account(pool, cursor, uid, account_number, log,
fail=True)
if not bank_account:
return False
if bank_account.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,
))
return False
results.account = bank_account
bank_settings_obj = pool.get('account.banking.account.settings')
bank_settings_ids = bank_settings_obj.search(cursor, uid, [
('partner_bank_id', '=', bank_account.id)
])
if bank_settings_ids:
settings = bank_settings_obj.browse(cursor, uid, bank_settings_ids)[0]
results.journal_id = settings.journal_id
results.default_debit_account_id = settings.default_debit_account_id
results.default_credit_account_id = settings.default_credit_account_id
return results
def get_iban_bic_NL(bank_acc):
'''
Consult the Dutch online banking database to check both the account number
and the bank to which it belongs. Will not work offline, is limited to
banks operating in the Netherlands and will only convert Dutch local
account numbers.
'''
import urllib, urllib2
from BeautifulSoup import BeautifulSoup
IBANlink = 'http://www.ibannl.org/iban_check.php'
data = urllib.urlencode(dict(number=bank_acc, method='POST'))
request = urllib2.Request(IBANlink, data)
response = urllib2.urlopen(request)
soup = BeautifulSoup(response)
result = struct()
for _pass, td in enumerate(soup.findAll('td')):
if _pass % 2 == 1:
result[attr] = td.find('font').contents[0]
else:
attr = td.find('strong').contents[0][:4].strip().lower()
if result:
result.account = bank_acc
result.country_id = result.bic[4:6]
# Nationalized bank code
result.code = result.bic[:6]
# All Dutch banks use generic channels
result.bic += 'XXX'
return result
return None
online_account_info = {
# TODO: Add more online data banks
'NL': get_iban_bic_NL,
}
def create_bank_account(pool, cursor, uid, partner_id,
account_number, holder_name, log
):
'''
Create a matching bank account with this holder for this partner.
'''
values = struct(
partner_id = partner_id,
owner_name = holder_name,
)
# Are we dealing with IBAN?
iban = sepa.IBAN(account_number)
if iban.valid:
values.state = 'iban'
values.acc_number = iban.BBAN
bankcode = iban.bankcode + iban.countrycode
else:
# No, try to convert to IBAN
country = pool.get('res.partner').browse(
cursor, uid, partner_id).country_id
values.state = 'bank'
values.acc_number = account_number
if country.code in sepa.IBAN.countries \
and country.code in online_account_info \
:
account_info = online_account_info[country.code](values.acc_number)
if account_info and iban in account_info:
values.iban = iban = account_info.iban
values.state = 'iban'
bankcode = account_info.code
bic = account_info.bic
else:
bankcode = None
bic = None
if bankcode:
# Try to link bank
bank_obj = pool.get('res.bank')
bank_ids = bank_obj.search(cursor, uid, [
('code', 'ilike', bankcode)
])
if not bank_ids and bic:
bank_ids = bank_obj.search(cursor, uid, [
('bic', 'ilike', bic)
])
if bank_ids:
# Check BIC on existing banks
values.bank_id = bank_ids[0]
bank = bank_obj.browse(cursor, uid, values.bank_id)
if not bank.bic:
bank_obj.write(cursor, uid, values.bank_id, dict(bic=bic))
else:
# New bank - create
values.bank_id = bank_obj.create(cursor, uid, dict(
code = account_info.code,
bic = account_info.bic,
name = account_info.bank,
country_id = country.id,
))
# Create bank account and return
return pool.get('res.partner.bank').create(cursor, uid, values)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: