credativ team mailing list archive
-
credativ team
-
Mailing list archive
-
Message #03527
lp:~therp-nl/banking-addons/6.0-interactive-matching-and-debit-orders into lp:banking-addons
Stefan Rijnhart (Therp) has proposed merging lp:~therp-nl/banking-addons/6.0-interactive-matching-and-debit-orders into lp:banking-addons.
Requested reviews:
Banking Addons Team (banking-addons-team)
For more details, see:
https://code.launchpad.net/~therp-nl/banking-addons/6.0-interactive-matching-and-debit-orders/+merge/88832
Hi,
this merge request is for review and commenting. I suggest not to merge the branch with 6.0, but to use it as a starting point for the 6.1 branch of banking-addons.
Cheers,
Stefan.
--
https://code.launchpad.net/~therp-nl/banking-addons/6.0-interactive-matching-and-debit-orders/+merge/88832
Your team Banking Addons Team is requested to review the proposed merge of lp:~therp-nl/banking-addons/6.0-interactive-matching-and-debit-orders into lp:banking-addons.
=== modified file 'account_banking/__init__.py'
--- account_banking/__init__.py 2010-02-03 23:36:03 +0000
+++ account_banking/__init__.py 2012-01-17 10:38:36 +0000
@@ -26,6 +26,7 @@
##############################################################################
import sepa
import record
+import banking_import_transaction
import account_banking
import parsers
import wizard
=== renamed file 'account_banking/__terp__.py' => 'account_banking/__openerp__.py'
--- account_banking/__terp__.py 2011-07-21 11:30:59 +0000
+++ account_banking/__openerp__.py 2012-01-17 10:38:36 +0000
@@ -1,6 +1,11 @@
##############################################################################
#
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
+# (C) 2011 Therp BV (<http://therp.nl>).
+# (C) 2011 Smile (<http://smile.fr>).
+#
+# All other contributions are (C) by their respective contributors
+#
# All Rights Reserved
#
# WARNING: This program as such is intended to be used by professional
@@ -25,11 +30,11 @@
##############################################################################
{
'name': 'Account Banking',
- 'version': '0.1.62',
+ 'version': '0.1.102',
'license': 'GPL-3',
- 'author': 'EduSense BV',
- 'website': 'http://www.edusense.nl',
- 'category': 'Account Banking',
+ 'author': 'Banking addons community',
+ 'website': 'https://launchpad.net/banking-addons',
+ 'category': 'Banking addons',
'depends': ['base', 'account', 'base_iban', 'account_payment'],
'init_xml': [],
'update_xml': [
@@ -38,6 +43,8 @@
'wizard/bank_import_view.xml',
'account_banking_view.xml',
'account_banking_workflow.xml',
+ 'wizard/banking_transaction_wizard.xml',
+ 'workflow/account_invoice.xml',
],
'demo_xml': [],
'description': '''
=== modified file 'account_banking/account_banking.py'
--- account_banking/account_banking.py 2011-11-01 15:28:18 +0000
+++ account_banking/account_banking.py 2012-01-17 10:38:36 +0000
@@ -170,6 +170,7 @@
'state': fields.selection(
[('unfinished', 'Unfinished'),
('error', 'Error'),
+ ('review', 'Review'),
('ready', 'Finished'),
], 'State', select=True, readonly=True
),
@@ -209,8 +210,16 @@
'Leave empty for manual processing'),
domain=[('osv_memory', '=', True)],
),
+ 'payment_order_type': fields.selection(
+ [('payment', 'Payment'),('debit', 'Direct debit')],
+ 'Payment order type', required=True,
+ ),
}
+ _defaults = {
+ 'payment_order_type': lambda *a: 'payment',
+ }
+
payment_mode_type()
class payment_mode(osv.osv):
@@ -444,7 +453,9 @@
"""
if st_line.reconcile_id:
account_move_line_obj.write(cr, uid, [torec], {
- 'reconcile_id': st_line.reconcile_id.id }, context=context)
+ (st_line.reconcile_id.line_partial_ids and
+ 'reconcile_partial_id' or 'reconcile_id'):
+ st_line.reconcile_id.id }, context=context)
for move_line in (st_line.reconcile_id.line_id or []) + (
st_line.reconcile_id.line_partial_ids or []):
netsvc.LocalService("workflow").trg_trigger(
@@ -533,10 +544,13 @@
res = {}
for st_line in self.browse(cr, uid, ids, context):
res[st_line.id] = False
- for move_line in (st_line.reconcile_id and
- (st_line.reconcile_id.line_id or []) +
- (st_line.reconcile_id.line_partial_ids or []) or
- []):
+ for move_line in (
+ st_line.reconcile_id and
+ (st_line.reconcile_id.line_id or
+ st_line.reconcile_id.line_partial_ids) or
+ st_line.import_transaction_id and
+ st_line.import_transaction_id.move_line_id and
+ [st_line.import_transaction_id.move_line_id] or []):
if move_line.invoice:
res[st_line.id] = move_line.invoice.id
continue
@@ -566,14 +580,14 @@
states={'draft':[('readonly', False)]},
),
'period_id': fields.many2one('account.period', 'Period', required=True,
- states={'confirm': [('readonly', True)]}),
+ states={'confirmed': [('readonly', True)]}),
'currency': fields.many2one('res.currency', 'Currency', required=True,
- states={'confirm': [('readonly', True)]}),
+ states={'confirmed': [('readonly', True)]}),
# Not used yet, but usefull in the future.
'international': fields.boolean('International Transaction',
required=False,
- states={'confirm': [('readonly', True)]},
+ states={'confirmed': [('readonly', True)]},
),
'reconcile_id': fields.many2one(
'account.move.reconcile', 'Reconciliation', readonly=True
@@ -743,6 +757,45 @@
return res
+ """
+ Hooks for processing direct debit orders, such as implemented in
+ account_direct_debit module.
+ """
+ def get_storno_account_id(self, cr, uid, payment_line_id, amount,
+ currency_id, context=None):
+ """
+ Hook for verifying a match of the payment line with the amount.
+ Return the account associated with the storno.
+ Used in account_banking interactive mode
+ :param payment_line_id: the single payment line id
+ :param amount: the (signed) amount debited from the bank account
+ :param currency: the bank account's currency *browse object*
+ :return: an account if there is a full match, False otherwise
+ :rtype: database id of an account.account resource.
+ """
+
+ return False
+
+ def debit_storno(self, cr, uid, payment_line_id, amount,
+ currency_id, storno_retry=True, context=None):
+ """
+ Hook for handling a canceled item of a direct debit order.
+ Presumably called from a bank statement import routine.
+
+ Decide on the direction that the invoice's workflow needs to take.
+ You may optionally return an incomplete reconcile for the caller
+ to reconcile the now void payment.
+
+ :param payment_line_id: the single payment line id
+ :param amount: the (negative) amount debited from the bank account
+ :param currency: the bank account's currency *browse object*
+ :param boolean storno_retry: whether the storno is considered fatal \
+ or not.
+ :return: an incomplete reconcile for the caller to fill
+ :rtype: database id of an account.move.reconcile resource.
+ """
+
+ return False
payment_line()
@@ -777,7 +830,7 @@
'rejected': [('readonly', True)],
'done': [('readonly', True)]
},
- help='Select the Payment Mode to be applied.'
+ help='Select the Payment Mode to be applied.',
),
'state': fields.selection([
('draft', 'Draft'),
@@ -820,8 +873,17 @@
"execution."
)
),
+ 'payment_order_type': fields.selection(
+ [('payment', 'Payment'),('debit', 'Direct debit')],
+ 'Payment order type', required=True,
+ ),
+ 'date_sent': fields.date('Send date', readonly=True),
}
+ _defaults = {
+ 'payment_order_type': lambda *a: 'payment',
+ }
+
def launch_wizard(self, cr, uid, ids, context=None):
"""
Search for a wizard to launch according to the type.
@@ -860,7 +922,9 @@
_('You can only combine payment orders of the same type')
)
# process manual payments
- self.action_sent(cr, uid, ids, context)
+ wf_service = netsvc.LocalService('workflow')
+ for order_id in ids:
+ wf_service.trg_validate(uid, 'payment.order', order_id, 'sent', cr)
return result
def _write_payment_lines(self, cursor, uid, ids, **kwargs):
@@ -893,9 +957,8 @@
Set both self and payment lines to state 'sent'.
'''
self._write_payment_lines(cursor, uid, ids, export_state='sent')
- wf_service = netsvc.LocalService('workflow')
- for id in ids:
- wf_service.trg_validate(uid, 'payment.order', id, 'sent', cursor)
+ self.write(cursor, uid, ids, {'state':'sent',
+ 'date_sent': time.strftime('%Y-%m-%d')})
return True
def action_rejected(self, cursor, uid, ids, *args):
@@ -931,6 +994,30 @@
return 'account_banking', 'wizard_account_banking_payment_manual'
return super(payment_order, self).get_wizard(type)
+ """
+ Hooks for processing direct debit orders, such as implemented in
+ account_direct_debit module.
+ """
+ def debit_reconcile_transfer(
+ self, cr, uid, payment_order_id, amount, currency, context=None):
+ """
+ Reconcile the payment order if the amount is correct. Return the
+ id of the reconciliation.
+ """
+ raise osv.except_osv(
+ _("Cannot reconcile"),
+ _("Cannot reconcile debit order: "+
+ "Not implemented."))
+
+ def debit_unreconcile_transfer(
+ self, cr, uid, payment_order_id, reconcile_id, amount, currency,
+ context=None):
+ """ Unreconcile the payment_order if at all possible """
+ raise osv.except_osv(
+ _("Cannot unreconcile"),
+ _("Cannot unreconcile debit order: "+
+ "Not implemented."))
+
payment_order()
class res_partner_bank(osv.osv):
@@ -1331,6 +1418,16 @@
'''
_inherit = 'account.invoice'
+ def test_undo_paid(self, cr, uid, ids, context=None):
+ """
+ Called from the workflow. Used to unset paid state on
+ invoices that were paid with bank transfers which are being cancelled
+ """
+ for invoice in self.read(cr, uid, ids, ['reconciled'], context):
+ if invoice['reconciled']:
+ return False
+ return True
+
def _get_reference_type(self, cr, uid, context=None):
'''
Return the list of reference types
@@ -1347,4 +1444,21 @@
invoice()
+class account_move_line(osv.osv):
+ _inherit = "account.move.line"
+
+ def get_balance(self, cr, uid, ids, context=None):
+ """
+ Return the balance of any set of move lines.
+ Surely this exists somewhere in account base, but I missed it.
+ """
+ total = 0.0
+ if not ids:
+ total
+ for line in self.read(
+ cr, uid, ids, ['debit', 'credit'], context=context):
+ total += (line['debit'] or 0.0) - (line['credit'] or 0.0)
+ return total
+account_move_line()
+
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
=== modified file 'account_banking/account_banking_view.xml'
--- account_banking/account_banking_view.xml 2011-07-21 11:30:59 +0000
+++ account_banking/account_banking_view.xml 2012-01-17 10:38:36 +0000
@@ -176,10 +176,14 @@
<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="sequence" eval="60"/>
<field name="type">form</field>
<field name="arch" type="xml">
<data>
<field name="period_id" position="replace"/>
+ <xpath expr="/form/notebook/page[@string='Transaction']/field/tree" position="attributes">
+ <attribute name="colors">black:state == 'confirmed';darkmagenta:match_multi == True;grey:state=='draft';crimson:duplicate == True;</attribute>
+ </xpath>
<xpath expr="/form/notebook/page[@string='Transaction']/field/tree/field[@name='name']" position="replace">
<field name="name" required="1"/>
</xpath>
@@ -206,9 +210,14 @@
<field name="model">account.bank.statement</field>
<field name="type">form</field>
<field name="arch" type="xml">
- <xpath expr="/form/notebook/page[@string='Transaction']/field/form/field[@name='ref']" position="after">
- <field name="period_id"/>
- </xpath>
+ <data>
+ <xpath expr="/form/notebook/page[@string='Transaction']/field/form/field[@name='ref']" position="after">
+ <field name="period_id"/>
+ <field name="match_type"/>
+ <field name="match_multi"/>
+ <field name="duplicate"/>
+ </xpath>
+ </data>
</field>
</record>
@@ -253,9 +262,27 @@
<field name="arch" type="xml">
<data>
<xpath expr="/form/notebook/page/field[@name='line_ids']/tree/field[@name='partner_id']" position="after">
+ <!-- TODO set partner_id when partner_bank_id changes -->
<field name="partner_bank_id"/>
</xpath>
<xpath expr="/form/notebook/page/field[@name='line_ids']/tree/field[@name='amount']" position="after">
+ <field name="match_type"/>
+ <field name="residual"/>
+ <button name="match_wizard" states="draft"
+ string="Match"
+ icon="terp-gtk-jump-to-ltr"
+ type="object"/>
+ <field name="match_multi" invisible="1"/>
+ <field name="duplicate" invisible="1"/>
+ <field name="state"/>
+ <button name="confirm" states="draft"
+ string="Confirm transaction"
+ icon="gtk-ok"
+ type="object"/>
+ <button name="cancel" states="confirmed"
+ string="Cancel transaction"
+ icon="gtk-cancel"
+ type="object"/>
<field name="invoice_id"/>
<field name="reconcile_id"/>
</xpath>
@@ -423,9 +450,104 @@
<field name="name" />
<field name="code" />
<field name="suitable_bank_types"/>
+ <field name="payment_order_type"/>
<field name="ir_model_id"/>
</form>
</field>
</record>
- </data>
+
+ <!-- fixes https://bugs.launchpad.net/openobject-addons/+bug/903156 for 6.0
+ Note that 6.1 does not suffer from the problem
+ -->
+ <record id="account_payment.action_create_payment_order" model="ir.actions.act_window">
+ <field name="view_id" ref="account_payment.view_create_payment_order"/>
+ </record>
+
+
+ <record model="ir.ui.view" id="view_bank_statement_line_tree">
+ <field name="name">Bank statement line tree view</field>
+ <field name="model">account.bank.statement.line</field>
+ <field name="type">tree</field>
+ <field name="arch" type="xml">
+ <tree string="Statement lines" colors="black:state == 'confirmed';darkmagenta:match_multi == True;crimson:duplicate == True;grey:state=='draft';">
+ <field name="sequence" readonly="1" invisible="1"/>
+ <field name="date" groups="base.group_extended"/>
+ <field name="name"/>
+ <field name="ref"/>
+ <field name="partner_id" on_change="onchange_partner_id(partner_id)"/>
+ <!-- TODO set partner_id when partner_bank_id changes -->
+ <field name="partner_bank_id"/>
+ <field name="type" on_change="onchange_type(partner_id, type)"/>
+ <!-- TODO note the references to parent from the statement form view -->
+ <field domain="[('journal_id','=',parent.journal_id)]" name="account_id"/>
+ <field name="analytic_account_id" groups="analytic.group_analytic_accounting" domain="[('company_id', '=', parent.company_id), ('type', '<>', 'view')]"/>
+ <field name="amount"/>
+ <field name="match_type"/>
+ <field name="residual"/>
+ <button name="match_wizard" states="draft"
+ string="Match"
+ icon="terp-gtk-jump-to-ltr"
+ type="object"/>
+ <field name="match_multi" invisible="1"/>
+ <field name="duplicate" invisible="1"/>
+ <field name="state"/>
+ <button name="confirm" states="draft"
+ string="Confirm transaction"
+ icon="gtk-ok"
+ type="object"/>
+ <button name="cancel" states="confirmed"
+ string="Cancel transaction"
+ icon="gtk-cancel"
+ type="object"/>
+ <field name="invoice_id"/>
+ <field name="reconcile_id"/>
+ </tree>
+ </field>
+ </record>
+
+ <!-- search view for bank statement lines -->
+ <record id="view_account_bank_statement_line_search" model="ir.ui.view">
+ <field name="name">account.bank.statement.line.search</field>
+ <field name="model">account.bank.statement.line</field>
+ <field name="type">search</field>
+ <field name="arch" type="xml">
+ <search string="Search Bank Transactions ">
+ <group>
+ <filter string="Draft" domain="[('state','=','draft')]" icon="terp-camera_test"/>
+ <filter string="Confirmed" domain="[('state','=','confirmed')]" icon="terp-dialog-close"/>
+ <separator orientation="vertical"/>
+ <field name="ref"/>
+ <field name="type"/>
+ <field name="period_id"/>
+ </group>
+ <newline/>
+ <group>
+ <field name="date"/>
+ <field name="partner_id"/>
+ <field name="partner_bank_id"/>
+ <field name="account_id"/>
+ </group>
+ <newline/>
+ <group expand="0" string="Group By...">
+ <filter string="State" context="{'group_by': 'state'}" icon="terp-stock_effects-object-colorize"/>
+ </group>
+ </search>
+ </field>
+ </record>
+
+
+ <!-- Add a shortcut menu for bank accounts -->
+ <record model="ir.actions.act_window" id="action_bank_statement_line_tree">
+ <field name="name">Bank Transactions</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">account.bank.statement.line</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="search_view_id" ref="view_account_bank_statement_line_search"/>
+ </record>
+ <menuitem string="Bank Transactions"
+ action="action_bank_statement_line_tree"
+ id="menu_bank_statement_line_tree"
+ parent="account.menu_finance_bank_and_cash" sequence="8"/>
+ </data>
</openerp>
=== modified file 'account_banking/account_banking_workflow.xml'
--- account_banking/account_banking_workflow.xml 2010-06-29 14:58:52 +0000
+++ account_banking/account_banking_workflow.xml 2012-01-17 10:38:36 +0000
@@ -10,8 +10,7 @@
<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="action">action_sent()</field>
<field name="kind">function</field>
</record>
<!-- New activity for workflow payment order: rejected -->
=== added file 'account_banking/banking_import_transaction.py'
--- account_banking/banking_import_transaction.py 1970-01-01 00:00:00 +0000
+++ account_banking/banking_import_transaction.py 2012-01-17 10:38:36 +0000
@@ -0,0 +1,1845 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
+# Contributions by Kaspars Vilkens (KNdati):
+# lenghty discussions, bugreports and bugfixes
+# Refractoring (C) 2011 Therp BV (<http://therp.nl>).
+# (C) 2011 Smile (<http://smile.fr>).
+#
+# 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 osv import osv, fields
+import time
+import netsvc
+import base64
+import datetime
+from tools import config
+from tools.translate import _
+from parsers import models
+from parsers.convert import *
+# from account_banking.struct import struct
+from account_banking import sepa
+from wizard.banktools import *
+import decimal_precision as dp
+
+bt = models.mem_bank_transaction
+
+class banking_import_transaction(osv.osv):
+ """ orm representation of mem_bank_transaction() for interactive and posthoc
+ configuration of reconciliation in the bank statement view.
+
+ Possible refractoring in OpenERP 6.1:
+ merge with bank_statement_line, using sparse fields
+
+ """
+ _name = 'banking.import.transaction'
+ _description = 'Bank import transaction'
+ _rec_name = 'transaction'
+
+ # This variable is used to match supplier invoices with an invoice date after
+ # the real payment date. This can occur with online transactions (web shops).
+ # TODO: Convert this to a proper configuration variable
+ payment_window = datetime.timedelta(days=10)
+
+ def _match_costs(self, cr, uid, trans, period_id, account_info, log):
+ '''
+ Get or create a costs invoice for the bank and return it with
+ the payment as seen in the transaction (when not already done).
+ '''
+ if not account_info.costs_account_id:
+ return []
+
+ digits = dp.get_precision('Account')(cr)[1]
+ amount = round(abs(trans.transferred_amount), digits)
+ # Make sure to be able to pinpoint our costs invoice for later
+ # matching
+ reference = '%s.%s: %s' % (trans.statement, trans.transaction, trans.reference)
+
+ # search supplier invoice
+ invoice_obj = self.pool.get('account.invoice')
+ invoice_ids = invoice_obj.search(cr, uid, [
+ '&',
+ ('type', '=', 'in_invoice'),
+ ('partner_id', '=', account_info.bank_partner_id.id),
+ ('company_id', '=', account_info.company_id.id),
+ ('date_invoice', '=', trans.effective_date),
+ ('reference', '=', reference),
+ ('amount_total', '=', amount),
+ ]
+ )
+ if invoice_ids and len(invoice_ids) == 1:
+ invoice = invoice_obj.browse(cr, uid, invoice_ids)[0]
+ elif not invoice_ids:
+ # create supplier invoice
+ partner_obj = self.pool.get('res.partner')
+ invoice_lines = [(0,0,dict(
+ amount = 1,
+ price_unit = amount,
+ name = trans.message or trans.reference,
+ account_id = account_info.costs_account_id.id
+ ))]
+ invoice_address_id = partner_obj.address_get(
+ cr, uid, [account_info.bank_partner_id.id], ['invoice']
+ )
+ invoice_id = invoice_obj.create(cr, uid, dict(
+ type = 'in_invoice',
+ company_id = account_info.company_id.id,
+ partner_id = account_info.bank_partner_id.id,
+ address_invoice_id = invoice_address_id['invoice'],
+ period_id = period_id,
+ journal_id = account_info.invoice_journal_id.id,
+ account_id = account_info.bank_partner_id.property_account_payable.id,
+ date_invoice = trans.effective_date,
+ reference_type = 'none',
+ reference = reference,
+ name = trans.reference or trans.message,
+ check_total = amount,
+ invoice_line = invoice_lines,
+ ))
+ invoice = invoice_obj.browse(cr, uid, invoice_id)
+ # Create workflow
+ invoice_obj.button_compute(cr, uid, [invoice_id],
+ {'type': 'in_invoice'}, set_total=True)
+ wf_service = netsvc.LocalService('workflow')
+ # Move to state 'open'
+ wf_service.trg_validate(uid, 'account.invoice', invoice.id,
+ 'invoice_open', cr)
+
+ # return move_lines to mix with the rest
+ return [x for x in invoice.move_id.line_id if x.account_id.reconcile]
+
+ def _match_debit_order(
+ self, cr, uid, trans, log, context=None):
+
+ def is_zero(total):
+ return self.pool.get('res.currency').is_zero(
+ cr, uid, trans.statement_id.currency, total)
+
+ payment_order_obj = self.pool.get('payment.order')
+ order_ids = payment_order_obj.search(
+ cr, uid, [('payment_order_type', '=', 'debit'),
+ ('state', '=', 'sent'),
+ ('date_sent', '<=', str2date(trans.execution_date,
+ '%Y-%m-%d'))
+ ],
+ limit=0, context=context)
+ orders = payment_order_obj.browse(cr, uid, order_ids, context)
+ candidates = [x for x in orders if
+ is_zero(x.total - trans.transferred_amount)]
+ if len(candidates) > 0:
+ # retrieve the common account_id, if any
+ account_id = False
+ for line in candidates[0].line_ids[0].debit_move_line_id.move_id.line_id:
+ if line.account_id.type == 'other':
+ account_id = line.account_id.id
+ break
+ return dict(
+ move_line_ids = False,
+ match_type = 'payment_order',
+ payment_order_ids = [x.id for x in candidates],
+ account_id = account_id,
+ partner_id = False,
+ partner_bank_id = False,
+ reference = False,
+ type='general',
+ )
+ return False
+
+ def _match_invoice(self, cr, uid, trans, move_lines,
+ partner_ids, bank_account_ids,
+ log, linked_invoices,
+ context=None):
+ '''
+ 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.
+ 2. Debit amounts are either customer invoices or credited supplier
+ invoices.
+ 3. Credit amounts are either supplier invoices or credited customer
+ invoices.
+ 4. Payments are either below expected amount or only slightly above
+ (abs).
+ 5. 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.
+
+ TODO: REVISE THIS DOC
+ #Return values:
+ # old_trans: this function can modify and rebrowse the modified
+ # transaction.
+ # move_info: the move_line information belonging to the matched
+ # invoice
+ # new_trans: the new transaction when the current one was split.
+ # This can happen when multiple invoices were paid with a single
+ # bank transaction.
+ '''
+
+ def eyecatcher(invoice):
+ '''
+ Return the eyecatcher for an invoice
+ '''
+ return invoice.type.startswith('in_') and invoice.name or \
+ invoice.number
+
+ def has_id_match(invoice, ref, msg):
+ '''
+ Aid for debugging - way more comprehensible than complex
+ comprehension filters ;-)
+
+ Match on ID of invoice (reference, name or number, whatever
+ available and sensible)
+ '''
+ if invoice.reference:
+ # Reference always comes first, as it is manually set for a
+ # reason.
+ iref = invoice.reference.upper()
+ if iref in ref or iref in msg:
+ return True
+ if invoice.type.startswith('in_'):
+ # Internal numbering, no likely match on number
+ if invoice.name:
+ iname = invoice.name.upper()
+ if iname in ref or iname in msg:
+ return True
+ elif invoice.type.startswith('out_'):
+ # External id's possible and likely
+ inum = invoice.number.upper()
+ if inum in ref or inum in msg:
+ return True
+
+ return False
+
+ def _cached(move_line):
+ # Disabled, we allow for multiple matches in
+ # the interactive wizard
+ return False
+
+ '''Check if the move_line has been cached'''
+ return move_line.id in linked_invoices
+
+ def _cache(move_line, remaining=0.0):
+ '''Cache the move_line'''
+ linked_invoices[move_line.id] = remaining
+
+ def _remaining(move_line):
+ '''Return the remaining amount for a previously matched move_line
+ '''
+ return linked_invoices[move_line.id]
+
+ def _sign(invoice):
+ '''Return the direction of an invoice'''
+ return {'in_invoice': -1,
+ 'in_refund': 1,
+ 'out_invoice': 1,
+ 'out_refund': -1
+ }[invoice.type]
+
+ def is_zero(move_line, total):
+ return self.pool.get('res.currency').is_zero(
+ cr, uid, trans.statement_id.currency, total)
+
+ digits = dp.get_precision('Account')(cr)[1]
+ partial = False
+
+ # Search invoice on partner
+ if partner_ids:
+ candidates = [
+ x for x in move_lines
+ if x.partner_id.id in partner_ids and
+ (str2date(x.date, '%Y-%m-%d') <=
+ (str2date(trans.execution_date, '%Y-%m-%d') +
+ self.payment_window))
+ and (not _cached(x) or _remaining(x))
+ ]
+ else:
+ candidates = []
+
+ # 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 len(candidates) > 1 or not candidates:
+ ref = trans.reference.upper()
+ msg = trans.message.upper()
+ # The manual usage of the sales journal creates moves that
+ # are not tied to invoices. Thanks to Stefan Rijnhart for
+ # reporting this.
+ candidates = [
+ x for x in candidates or move_lines
+ if (x.invoice and has_id_match(x.invoice, ref, msg) and
+ str2date(x.invoice.date_invoice, '%Y-%m-%d') <=
+ (str2date(trans.execution_date, '%Y-%m-%d') +
+ self.payment_window)
+ and (not _cached(x) or _remaining(x)))
+ ]
+
+ # Match on amount expected. Limit this kind of search to known
+ # partners.
+ if not candidates and partner_ids:
+ candidates = [
+ x for x in move_lines
+ if (is_zero(x.move_id, ((x.debit or 0.0) - (x.credit or 0.0)) -
+ trans.transferred_amount)
+ and str2date(x.date, '%Y-%m-%d') <=
+ (str2date(trans.execution_date, '%Y-%m-%d') +
+ self.payment_window)
+ and (not _cached(x) or _remaining(x)))
+ ]
+
+ move_line = False
+
+ if candidates and len(candidates) > 0:
+ # Now a possible selection of invoices has been found, check the
+ # amounts expected and received.
+ #
+ # TODO: currency coercing
+ best = [x for x in candidates
+ if (is_zero(x.move_id, ((x.debit or 0.0) - (x.credit or 0.0)) -
+ trans.transferred_amount)
+ and str2date(x.date, '%Y-%m-%d') <=
+ (str2date(trans.execution_date, '%Y-%m-%d') +
+ self.payment_window))
+ ]
+ if len(best) == 1:
+ # Exact match
+ move_line = best[0]
+ invoice = move_line.invoice
+ if _cached(move_line):
+ partial = True
+ expected = _remaining(move_line)
+ else:
+ _cache(move_line)
+
+ elif len(candidates) > 1:
+ # Before giving up, check cache for catching duplicate
+ # transfers first
+ paid = [x for x in move_lines
+ if x.invoice and has_id_match(x.invoice, ref, msg)
+ and str2date(x.invoice.date_invoice, '%Y-%m-%d')
+ <= str2date(trans.execution_date, '%Y-%m-%d')
+ and (_cached(x) and not _remaining(x))
+ ]
+ if paid:
+ log.append(
+ _('Unable to link transaction id %(trans)s '
+ '(ref: %(ref)s) to invoice: '
+ 'invoice %(invoice)s was already paid') % {
+ 'trans': '%s.%s' % (trans.statement, trans.transaction),
+ 'ref': trans.reference,
+ 'invoice': eyecatcher(paid[0].invoice)
+ })
+ else:
+ # Multiple matches
+ # TODO select best bank account in this case
+ return (trans, self._get_move_info(
+ cr, uid, [x.id for x in candidates]),
+ False)
+ move_line = False
+ partial = False
+
+ elif len(candidates) == 1:
+ # Mismatch in amounts
+ move_line = candidates[0]
+ invoice = move_line.invoice
+ expected = round(_sign(invoice) * invoice.residual, digits)
+ partial = True
+
+ trans2 = None
+ if move_line and partial:
+ found = round(trans.transferred_amount, digits)
+ if abs(expected) == abs(found):
+ partial = False
+ # Last partial payment will not flag invoice paid without
+ # manual assistence
+ # Stefan: disabled this here for the interactive method
+ # Handled this with proper handling of partial reconciliation
+ # and the workflow service
+ # invoice_obj = self.pool.get('account.invoice')
+ # invoice_obj.write(cr, uid, [invoice.id], {
+ # 'state': 'paid'
+ # })
+ elif abs(expected) > abs(found):
+ # Partial payment, reuse invoice
+ _cache(move_line, expected - found)
+ elif abs(expected) < abs(found):
+ # Disabled splitting transactions for now
+ # TODO allow splitting in the interactive wizard
+ pass
+
+ # Possible combined payments, need to split transaction to
+ # verify
+ _cache(move_line)
+ trans2 = self.copy(
+ cr, uid, trans.id,
+ dict(
+ transferred_amount = trans.transferred_amount - expected,
+ transaction = trans.transaction + 'b',
+ parent_id = trans.id,
+ ), context=context)
+ # update the current record
+ self.write(cr, uid, trans.id, dict(
+ transferred_amount = expected,
+ transaction = trans.transaction + 'a',
+ ), context)
+ # rebrowse the current record after writing
+ trans = self.browse(cr, uid, trans.id, context=context)
+ if move_line:
+ account_ids = [
+ x.id for x in bank_account_ids
+ if x.partner_id.id == move_line.partner_id.id
+ ]
+
+ return (trans, self._get_move_info(
+ cr, uid, [move_line.id],
+ account_ids and account_ids[0] or False),
+ trans2)
+
+ return trans, False, False
+
+ def _do_move_reconcile(
+ self, cr, uid, move_line_ids, currency, amount, context=None):
+ """
+ Prepare a reconciliation for a bank transaction of the given
+ amount. The caller MUST add the move line associated with the
+ bank transaction to the returned reconciliation resource.
+
+ If adding the amount does not make the total add up,
+ prepare a partial reconciliation. An existing reconciliation on
+ the move lines will be taken into account.
+
+ :param move_line_ids: List of ids. This will usually be the move
+ line of an associated invoice or payment, plus optionally the
+ move line of a writeoff.
+ :param currency: A res.currency *browse* object to perform math
+ operations on the amounts.
+ :param amount: the amount of the bank transaction. Amount < 0 in
+ case of a credit move on the bank account.
+ """
+ move_line_obj = self.pool.get('account.move.line')
+ reconcile_obj = self.pool.get('account.move.reconcile')
+ is_zero = lambda amount: self.pool.get('res.currency').is_zero(
+ cr, uid, currency, amount)
+ move_lines = move_line_obj.browse(cr, uid, move_line_ids, context=context)
+ reconcile = False
+ for move_line in move_lines:
+ if move_line.reconcile_id:
+ raise osv.except_osv(
+ _('Entry is already reconciled'),
+ _("You cannot reconcile the bank transaction with this entry, " +
+ "it is already reconciled")
+ )
+ if move_line.reconcile_partial_id:
+ if reconcile and reconcile.id != move_line.reconcile_partial_id.id:
+ raise osv.except_osv(
+ _('Cannot reconcile'),
+ _('Move lines are already partially reconciled, ' +
+ 'but not with each other.'))
+ reconcile = move_line.reconcile_partial_id
+ line_ids = list(set(move_line_ids + (
+ [x.id for x in reconcile and ( # reconcile.line_id or
+ reconcile.line_partial_ids) or []])))
+ if not reconcile:
+ reconcile_id = reconcile_obj.create(
+ cr, uid, {'type': 'auto' }, context=context)
+ reconcile = reconcile_obj.browse(cr, uid, reconcile_id, context=context)
+ full = is_zero(
+ move_line_obj.get_balance(cr, uid, line_ids) - amount)
+ # we should not have to check whether there is a surplus writeoff
+ # as any surplus amount *should* have been split off in the matching routine
+ if full:
+ line_partial_ids = []
+ else:
+ line_partial_ids = line_ids[:]
+ line_ids = []
+ reconcile_obj.write(
+ cr, uid, reconcile_id,
+ { 'line_id': [(6, 0, line_ids)],
+ 'line_partial_ids': [(6, 0, line_partial_ids)],
+ }, context=context)
+ return reconcile_id
+
+ def _do_move_unreconcile(self, cr, uid, move_line_ids, currency, context=None):
+ """
+ Undo a reconciliation, removing the given move line ids. If no
+ meaningful (partial) reconciliation remains, delete it.
+
+ :param move_line_ids: List of ids. This will usually be the move
+ line of an associated invoice or payment, plus optionally the
+ move line of a writeoff.
+ :param currency: A res.currency *browse* object to perform math
+ operations on the amounts.
+ """
+
+ move_line_obj = self.pool.get('account.move.line')
+ reconcile_obj = self.pool.get('account.move.reconcile')
+ is_zero = lambda amount: self.pool.get('res.currency').is_zero(
+ cr, uid, currency, amount)
+ move_lines = move_line_obj.browse(cr, uid, move_line_ids, context=context)
+ reconcile = move_lines[0].reconcile_id or move_lines[0].reconcile_partial_id
+ line_ids = [x.id for x in reconcile.line_id or reconcile.line_partial_ids]
+ for move_line_id in move_line_ids:
+ line_ids.remove(move_line_id)
+ if len(line_ids) > 1:
+ full = is_zero(move_line_obj.get_balance(cr, uid, line_ids))
+ if full:
+ line_partial_ids = []
+ else:
+ line_partial_ids = line_ids.copy()
+ line_ids = []
+ reconcile_obj.write(
+ cr, uid, reconcile.id,
+ { 'line_partial_ids': [(6, 0, line_ids)],
+ 'line_id': [(6, 0, line_partial_ids)],
+ }, context=context)
+ else:
+ reconcile_obj.unlink(cr, uid, reconcile.id, context=context)
+ for move_line in move_lines:
+ if move_line.invoice:
+ # reopening the invoice
+ netsvc.LocalService('workflow').trg_validate(
+ uid, 'account.invoice', move_line.invoice.id, 'undo_paid', cr)
+ return True
+
+ def _reconcile_move(
+ self, cr, uid, transaction_id, context=None):
+ transaction = self.browse(cr, uid, transaction_id, context=context)
+ if not transaction.move_line_id:
+ if transaction.match_type == 'invoice':
+ raise osv.except_osv(
+ _("Cannot link transaction %s with invoice") %
+ transaction.statement_line_id.name,
+ (transaction.invoice_ids and
+ (_("Please select one of the matches in transaction %s.%s") or
+ _("No match found for transaction %s.%s")) % (
+ transaction.statement_line_id.statement_id.name,
+ transaction.statement_line_id.name
+ )))
+ else:
+ raise osv.except_osv(
+ _("Cannot link transaction %s with accounting entry") %
+ transaction.statement_line_id.name,
+ (transaction.move_line_ids and
+ (_("Please select one of the matches in transaction %s.%s") or
+ _("No match found for transaction %s.%s")) % (
+ transaction.statement_line_id.statement_id.name,
+ transaction.statement_line_id.name
+ )))
+ currency = transaction.statement_line_id.statement_id.currency
+ line_ids = [transaction.move_line_id.id]
+ if transaction.writeoff_move_line_id:
+ line_ids.append(transaction.writeoff_move_line_id.id)
+ reconcile_id = self._do_move_reconcile(
+ cr, uid, line_ids, currency,
+ transaction.transferred_amount, context=context)
+ return reconcile_id
+
+ def _reconcile_storno(
+ self, cr, uid, transaction_id, context=None):
+ """
+ Creation of the reconciliation has been delegated to
+ *a* direct debit module, to allow for various direct debit styles
+ """
+ payment_line_obj = self.pool.get('payment.line')
+ transaction = self.browse(cr, uid, transaction_id, context=context)
+ if not transaction.payment_line_id:
+ raise osv.except_osv(
+ _("Cannot link with storno"),
+ _("No direct debit order item"))
+ return payment_line_obj.debit_storno(
+ cr, uid,
+ transaction.payment_line_id.id,
+ transaction.statement_line_id.amount,
+ transaction.statement_line_id.currency,
+ transaction.storno_retry,
+ context=context)
+
+ def _reconcile_payment_order(
+ self, cr, uid, transaction_id, context=None):
+ """
+ Creation of the reconciliation has been delegated to
+ *a* direct debit module, to allow for various direct debit styles
+ """
+ payment_order_obj = self.pool.get('payment.order')
+ transaction = self.browse(cr, uid, transaction_id, context=context)
+ if not transaction.payment_order_id:
+ raise osv.except_osv(
+ _("Cannot reconcile"),
+ _("Cannot reconcile: no direct debit order"))
+ if transaction.payment_order_id.payment_order_type != 'debit':
+ raise osv.except_osv(
+ _("Cannot reconcile"),
+ _("Reconcile payment order not implemented"))
+ return payment_order_obj.debit_reconcile_transfer(
+ cr, uid,
+ transaction.payment_order_id.id,
+ transaction.statement_line_id.amount,
+ transaction.statement_line_id.currency,
+ context=context)
+
+ def _reconcile_payment(
+ self, cr, uid, transaction_id, context=None):
+ """
+ Do some housekeeping on the payment line
+ then pass on to _reconcile_move
+ """
+ transaction = self.browse(cr, uid, transaction_id, context=context)
+ payment_line_obj = self.pool.get('payment.line')
+ payment_line_obj.write(
+ cr, uid, transaction.payment_line_id.id, {
+ 'export_state': 'done',
+ 'date_done': transaction.effective_date,
+ }
+ )
+ return self._reconcile_move(cr, uid, transaction_id, context=context)
+
+ def _cancel_payment(
+ self, cr, uid, transaction_id, context=None):
+ raise osv.except_osv(
+ _("Cannot unreconcile"),
+ _("Cannot unreconcile: this operation is not yet supported for "
+ "match type 'payment'"))
+
+ def _cancel_payment_order(
+ self, cr, uid, transaction_id, context=None):
+ """
+ """
+ payment_order_obj = self.pool.get('payment.order')
+ transaction = self.browse(cr, uid, transaction_id, context=context)
+ if not transaction.payment_order_id:
+ raise osv.except_osv(
+ _("Cannot unreconcile"),
+ _("Cannot unreconcile: no direct debit order"))
+ if transaction.payment_order_id.payment_order_type != 'debit':
+ raise osv.except_osv(
+ _("Cannot unreconcile"),
+ _("Unreconcile payment order not implemented"))
+ return payment_order_obj.debit_unreconcile_transfer(
+ cr, uid, transaction.payment_order_id.id,
+ transaction.statement_line_id.reconcile_id.id,
+ transaction.statement_line_id.amount,
+ transaction.statement_line_id.currency)
+
+ def _cancel_move(
+ self, cr, uid, transaction_id, context=None):
+ statement_line_obj = self.pool.get('account.bank.statement.line')
+ transaction = self.browse(cr, uid, transaction_id, context=context)
+ move_line_id = transaction.move_line_id.id
+ currency = transaction.statement_line_id.statement_id.currency
+ line_ids = [transaction.move_line_id.id]
+ if transaction.writeoff_move_line_id:
+ line_ids.append(transaction.writeoff_move_line_id.id)
+ self._do_move_unreconcile(
+ cr, uid, line_ids, currency, context=context)
+ statement_line_obj.write(
+ cr, uid, transaction.statement_line_id.id,
+ {'reconcile_id': False}, context=context)
+ return True
+
+ def _cancel_storno(
+ self, cr, uid, transaction_id, context=None):
+ """
+ TODO: delegate unreconciliation to the direct debit module,
+ to allow for various direct debit styles
+ """
+ payment_line_obj = self.pool.get('payment.line')
+ reconcile_obj = self.pool.get('account.move.reconcile')
+ transaction = self.browse(cr, uid, transaction_id, context=context)
+
+ if not transaction.payment_line_id:
+ raise osv.except_osv(
+ _("Cannot cancel link with storno"),
+ _("No direct debit order item"))
+ if not transaction.payment_line_id.storno:
+ raise osv.except_osv(
+ _("Cannot cancel link with storno"),
+ _("The direct debit order item is not marked for storno"))
+
+ journal = transaction.statement_line_id.statement_id.journal_id
+ if transaction.statement_line_id.amount >= 0:
+ account_id = journal.default_credit_account_id.id
+ else:
+ account_id = journal.default_debit_account_id.id
+ cancel_line = False
+ for line in transaction.statement_line_id.move_id.line_id:
+ if line.account_id.id != account_id:
+ cancel_line = line
+ break
+ if not cancel_line: # debug
+ raise osv.except_osv(
+ _("Cannot cancel link with storno"),
+ _("Line id not found"))
+ reconcile = cancel_line.reconcile_id or cancel_line.reconcile_partial_id
+ lines_reconcile = reconcile.line_id or reconcile.line_partial_ids
+ if len(lines_reconcile) < 3:
+ # delete the full reconciliation
+ reconcile_obj.unlink(cr, uid, reconcile.id, context)
+ else:
+ # we are left with a partial reconciliation
+ reconcile_obj.write(
+ cr, uid, reconcile.id,
+ {'line_partial_ids':
+ [(6, 0, [x.id for x in lines_reconcile if x.id != cancel_line.id])],
+ 'line_id': [(6, 0, [])],
+ }, context)
+ # redo the original payment line reconciliation with the invoice
+ payment_line_obj.write(
+ cr, uid, transaction.payment_line_id.id,
+ {'storno': False}, context)
+ payment_line_obj.debit_reconcile(
+ cr, uid, transaction.payment_line_id.id, context)
+
+ cancel_map = {
+ 'storno': _cancel_storno,
+ 'invoice': _cancel_move,
+ 'manual': _cancel_move,
+ 'move': _cancel_move,
+ 'payment_order': _cancel_payment_order,
+ 'payment': _cancel_payment,
+ }
+ def cancel(self, cr, uid, ids, context=None):
+ if ids and isinstance(ids, (int, float)):
+ ids = [ids]
+ move_obj = self.pool.get('account.move')
+ for transaction in self.browse(cr, uid, ids, context):
+ if not transaction.match_type:
+ continue
+ if transaction.match_type not in self.cancel_map:
+ raise osv.except_osv(
+ _("Cannot cancel type %s" % transaction.match_type),
+ _("No method found to cancel this type"))
+ self.cancel_map[transaction.match_type](
+ self, cr, uid, transaction.id, context)
+ # clear up the writeoff move
+ if transaction.writeoff_move_line_id:
+ move_obj.button_cancel(
+ cr, uid, [transaction.writeoff_move_line_id.move_id.id],
+ context=context)
+ move_obj.unlink(
+ cr, uid, [transaction.writeoff_move_line_id.move_id.id],
+ context=context)
+ return True
+
+ reconcile_map = {
+ 'storno': _reconcile_storno,
+ 'invoice': _reconcile_move,
+ 'manual': _reconcile_move,
+ 'payment_order': _reconcile_payment_order,
+ 'payment': _reconcile_payment,
+ 'move': _reconcile_move,
+ }
+ def reconcile(self, cr, uid, ids, context=None):
+ if ids and isinstance(ids, (int, float)):
+ ids = [ids]
+ for transaction in self.browse(cr, uid, ids, context):
+ if not transaction.match_type:
+ continue
+ if transaction.match_type not in self.reconcile_map:
+ raise osv.except_osv(
+ _("Cannot reconcile"),
+ _("Cannot reconcile type %s. No method found to " +
+ "reconcile this type") %
+ transaction.match_type
+ )
+ if (transaction.residual and transaction.writeoff_account_id):
+ if transaction.match_type not in ('invoice', 'move', 'manual'):
+ raise osv.except_osv(
+ _("Cannot reconcile"),
+ _("Bank transaction %s: write off not implemented for " +
+ "this match type.") %
+ transaction.statement_line_id.name
+ )
+ self._generate_writeoff_move(
+ cr, uid, transaction.id, context=context)
+ # run the method that is appropriate for this match type
+ reconcile_id = self.reconcile_map[transaction.match_type](
+ self, cr, uid, transaction.id, context)
+ self.pool.get('account.bank.statement.line').write(
+ cr, uid, transaction.statement_line_id.id,
+ {'reconcile_id': reconcile_id}, context=context)
+
+ # TODO
+ # update the statement line bank account reference
+ # as follows (from _match_invoice)
+
+ """
+ account_ids = [
+ x.id for x in bank_account_ids
+ if x.partner_id.id == move_line.partner_id.id
+ ][0]
+ """
+ return True
+
+
+ def _generate_writeoff_move(self, cr, uid, ids, context):
+ if ids and isinstance(ids, (int, float)):
+ ids = [ids]
+ move_line_obj = self.pool.get('account.move.line')
+ move_obj = self.pool.get('account.move')
+ for trans in self.browse(cr, uid, ids, context=context):
+ periods = self.pool.get('account.period').find(
+ cr, uid, trans.statement_line_id.date)
+ period_id = periods and periods[0] or False
+ move_id = move_obj.create(cr, uid, {
+ 'journal_id': trans.writeoff_journal_id.id,
+ 'period_id': period_id,
+ 'date': trans.statement_line_id.date,
+ 'name': '(write-off) %s' % (
+ trans.move_line_id.move_id.name or '')
+ }, context=context)
+ writeoff_debit = False
+ writeoff_credit = False
+ if trans.statement_line_id.amount > 0:
+ if trans.residual > 0:
+ writeoff_debit = trans.residual
+ else:
+ writeoff_credit = - trans.residual
+ else:
+ if trans.residual > 0:
+ writeoff_credit = trans.residual
+ else:
+ writeoff_debit = - trans.residual
+ vals = {
+ 'name': trans.statement_line_id.name,
+ 'date': trans.statement_line_id.date,
+ 'ref': trans.statement_line_id.ref,
+ 'move_id': move_id,
+ 'partner_id': (trans.statement_line_id.partner_id and
+ trans.statement_line_id.partner_id.id or False),
+ 'account_id': trans.statement_line_id.account_id.id,
+ 'credit': writeoff_debit,
+ 'debit': writeoff_credit,
+ 'journal_id': trans.writeoff_journal_id.id,
+ 'period_id': period_id,
+ 'currency_id': trans.statement_line_id.statement_id.currency.id,
+ }
+ move_line_id = move_line_obj.create(
+ cr, uid, vals, context=context)
+ self.write(
+ cr, uid, trans.id,
+ {'writeoff_move_line_id': move_line_id}, context=context)
+ vals.update({
+ 'account_id': trans.writeoff_account_id.id,
+ 'credit': writeoff_credit,
+ 'debit': writeoff_debit,
+ })
+ move_line_obj.create(
+ cr, uid, vals, context=context)
+ move_obj.post(
+ cr, uid, [move_id], context=context)
+
+ def _match_storno(
+ self, cr, uid, trans, log, context=None):
+ payment_line_obj = self.pool.get('payment.line')
+ line_ids = payment_line_obj.search(
+ cr, uid, [
+ ('order_id.payment_order_type', '=', 'debit'),
+ ('order_id.state', 'in', ['sent', 'done']),
+ ('communication', '=', trans.reference)
+ ], context=context)
+ # stornos MUST have an exact match
+ if len(line_ids) == 1:
+ account_id = payment_line_obj.get_storno_account_id(
+ cr, uid, line_ids[0], trans.transferred_amount,
+ trans.statement_id.currency, context=None)
+ if account_id:
+ return dict(
+ account_id = account_id,
+ match_type = 'storno',
+ payment_line_id = line_ids[0],
+ move_line_ids=False,
+ partner_id=False,
+ partner_bank_id=False,
+ reference=False,
+ type='customer',
+ )
+ # TODO log the reason why there is no result for transfers marked
+ # as storno
+ return False
+
+ def _match_payment(self, cr, uid, trans, payment_lines,
+ partner_ids, bank_account_ids, log, linked_payments):
+ '''
+ 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.
+ This also means that we do not allow for multiple candidates.
+ '''
+ # TODO: Not sure what side effects are created when payments are done
+ # for credited customer invoices, which will be matched later on too.
+ digits = dp.get_precision('Account')(cr)[1]
+ 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]
+ # Check cache to prevent multiple matching of a single payment
+ if candidate.id not in linked_payments:
+ linked_payments[candidate.id] = True
+ move_info = self._get_move_info(cr, uid, [candidate.move_line_id.id])
+ move_info.update({
+ 'match_type': 'payment',
+ 'payment_line_id': candidate.id,
+ })
+ return move_info
+
+ return False
+
+ signal_duplicate_keys = [
+ # does not include float values
+ # such as transferred_amount
+ 'execution_date', 'local_account', 'remote_account',
+ 'remote_owner', 'reference', 'message',
+ ]
+
+ def create(self, cr, uid, vals, context=None):
+ res = super(banking_import_transaction, self).create(
+ cr, uid, vals, context)
+ if res:
+ me = self.browse(cr, uid, res, context)
+ search_vals = [(key, '=', me[key])
+ for key in self.signal_duplicate_keys]
+ ids = self.search(cr, uid, search_vals, context=context)
+ dupes = []
+ # Test for transferred_amount seperately
+ # due to float representation and rounding difficulties
+ for trans in self.browse(cr, uid, ids, context=context):
+ if self.pool.get('res.currency').is_zero(
+ cr, uid,
+ trans.statement_id.currency,
+ me['transferred_amount'] - trans.transferred_amount):
+ dupes.append(trans.id)
+ if len(dupes) < 1:
+ raise osv.except_osv(_('Cannot check for duplicate'),
+ _("Cannot check for duplicate. "
+ "I can't find myself."))
+ if len(dupes) > 1:
+ self.write(
+ cr, uid, res, {'duplicate': True}, context=context)
+ return res
+
+ def split_off(self, cr, uid, res_id, amount, context=None):
+ # todo. Inherit the duplicate marker from res_id
+ pass
+
+ def combine(self, cr, uid, ids, context=None):
+ # todo. Check equivalence of primary key
+ pass
+
+ def _get_move_info(self, cr, uid, move_line_ids, partner_bank_id=False,
+ partial=False, match_type = False):
+ type_map = {
+ 'out_invoice': 'customer',
+ 'in_invoice': 'supplier',
+ 'out_refund': 'customer',
+ 'in_refund': 'supplier',
+ }
+ retval = {'partner_id': False,
+ 'partner_bank_id': partner_bank_id,
+ 'reference': False,
+ 'type': 'general',
+ 'move_line_ids': move_line_ids,
+ 'match_type': match_type,
+ 'account_id': False,
+ }
+ move_lines = self.pool.get('account.move.line').browse(cr, uid, move_line_ids)
+ for move_line in move_lines:
+ if move_line.partner_id:
+ if retval['partner_id']:
+ if retval['partner_id'] != move_line.partner_id.id:
+ retval['partner_id'] = False
+ break
+ else:
+ retval['partner_id'] = move_line.partner_id.id
+ else:
+ if retval['partner_id']:
+ retval['partner_id'] = False
+ break
+ for move_line in move_lines:
+ if move_line.account_id:
+ if retval['account_id']:
+ if retval['account_id'] != move_line.account_id.id:
+ retval['account_id'] = False
+ break
+ else:
+ retval['account_id'] = move_line.account_id.id
+ else:
+ if retval['account_id']:
+ retval['account_id'] = False
+ break
+ for move_line in move_lines:
+ if move_line.invoice:
+ if retval['match_type']:
+ if retval['match_type'] != 'invoice':
+ retval['match_type'] = False
+ break
+ else:
+ retval['match_type'] = 'invoice'
+ else:
+ if retval['match_type']:
+ retval['match_type'] = False
+ break
+ if move_lines and not retval['match_type']:
+ retval['match_type'] = 'move'
+ if move_lines and len(move_lines) == 1:
+ retval['reference'] = move_lines[0].ref
+ if retval['match_type'] == 'invoice':
+ retval['invoice_ids'] = [x.invoice.id for x in move_lines]
+ retval['type'] = type_map[move_lines[0].invoice.type]
+ return retval
+
+ def match(self, cr, uid, ids, results=None, context=None):
+ if not ids:
+ return True
+
+ company_obj = self.pool.get('res.company')
+ partner_bank_obj = self.pool.get('res.partner.bank')
+ journal_obj = self.pool.get('account.journal')
+ move_line_obj = self.pool.get('account.move.line')
+ payment_line_obj = self.pool.get('payment.line')
+ statement_line_obj = self.pool.get('account.bank.statement.line')
+ payment_order_obj = self.pool.get('payment.order')
+
+ # Results
+ if results is None:
+ results = dict(
+ trans_loaded_cnt = 0,
+ trans_skipped_cnt = 0,
+ trans_matched_cnt = 0,
+ bank_costs_invoice_cnt = 0,
+ error_cnt = 0,
+ log = [],
+ )
+
+ # Caching
+ error_accounts = {}
+ info = {}
+ linked_payments = {}
+ # TODO: harvest linked invoices from draft statement lines?
+ linked_invoices = {}
+ payment_lines = []
+
+ # 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.
+ # Strangely, payment_orders still do not have company_id
+ cr.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 cr.fetchall()]
+ if payment_line_ids:
+ payment_lines = payment_line_obj.browse(cr, uid, payment_line_ids)
+
+ # Start the loop over the transactions requested to match
+ transactions = self.browse(cr, uid, ids, context)
+ # TODO: do we do injected transactions here?
+ injected = []
+ i = 0
+ max_trans = len(transactions)
+ while i < max_trans:
+ move_info = False
+ if injected:
+ # Force FIFO behavior
+ transaction = injected.pop(0)
+ else:
+ transaction = transactions[i]
+
+ if (transaction.statement_line_id and
+ transaction.statement_line_id.state == 'confirmed'):
+ raise osv.except_osv(
+ _("Cannot perform match"),
+ _("Cannot perform match on a confirmed transction"))
+
+ if transaction.local_account in error_accounts:
+ results['trans_skipped_cnt'] += 1
+ if not injected:
+ i += 1
+ continue
+
+ # TODO: optimize by ordering transactions per company,
+ # and perform the stanza below only once per company.
+ # In that case, take newest transaction date into account
+ # when retrieving move_line_ids below.
+ company = company_obj.browse(
+ cr, uid, transaction.company_id.id, context)
+ # Get default defaults
+ def_pay_account_id = company.partner_id.property_account_payable.id
+ def_rec_account_id = company.partner_id.property_account_receivable.id
+
+ # Get interesting journals once
+ # Added type 'general' to capture fund transfers
+ journal_ids = journal_obj.search(cr, uid, [
+ ('type', 'in', ('general', 'sale','purchase',
+ 'purchase_refund','sale_refund')),
+ ('company_id', '=', company.id),
+ ])
+ # Get all unreconciled moves
+ move_line_ids = move_line_obj.search(cr, uid, [
+ ('reconcile_id', '=', False),
+ ('journal_id', 'in', journal_ids),
+ ('account_id.reconcile', '=', True),
+ ('date', '<=', transaction.execution_date),
+ ])
+ if move_line_ids:
+ move_lines = move_line_obj.browse(cr, uid, move_line_ids)
+ else:
+ move_lines = []
+
+ # Create fallback currency code
+ currency_code = transaction.local_currency or company.currency_id.name
+
+ # Check cache for account info/currency
+ if transaction.local_account in info and \
+ currency_code in info[transaction.local_account]:
+ account_info = info[transaction.local_account][currency_code]
+ else:
+ # Pull account info/currency
+ account_info = get_company_bank_account(
+ self.pool, cr, uid, transaction.local_account,
+ transaction.local_currency, company, results['log']
+ )
+ if not account_info:
+ results['log'].append(
+ _('Transaction found for unknown account %(bank_account)s') %
+ {'bank_account': transaction.local_account}
+ )
+ error_accounts[transaction.local_account] = True
+ results['error_cnt'] += 1
+ if not injected:
+ i += 1
+ continue
+ if 'journal_id' not in account_info:
+ results['log'].append(
+ _('Transaction found for account %(bank_account)s, '
+ 'but no default journal was defined.'
+ ) % {'bank_account': transaction.local_account}
+ )
+ error_accounts[transaction.local_account] = True
+ results['error_cnt'] += 1
+ if not injected:
+ i += 1
+ continue
+
+ # Get required currency code
+ currency_code = account_info.currency_id.name
+
+ # Cache results
+ if not transaction.local_account in info:
+ info[transaction.local_account] = {
+ currency_code: account_info
+ }
+ else:
+ info[transaction.local_account][currency_code] = account_info
+
+ # Final check: no coercion of currencies!
+ if transaction.local_currency \
+ and account_info.currency_id.name != transaction.local_currency:
+ # TODO: convert currencies?
+ results['log'].append(
+ _('transaction %(statement_id)s.%(transaction_id)s for account %(bank_account)s'
+ ' uses different currency than the defined bank journal.'
+ ) % {
+ 'bank_account': transactions.local_account,
+ 'transaction_id': transaction.statement,
+ 'statement_id': transaction.transaction,
+ }
+ )
+ error_accounts[transaction.local_account] = True
+ results['error_cnt'] += 1
+ if not injected:
+ i += 1
+ continue
+
+ # Link accounting period
+ period_id = get_period(
+ self.pool, cr, uid,
+ str2date(transaction.effective_date,'%Y-%m-%d'), company,
+ results['log'])
+ if not period_id:
+ results['trans_skipped_cnt'] += 1
+ if not injected:
+ i += 1
+ continue
+
+ # When bank costs are part of transaction itself, split it.
+ if transaction.type != bt.BANK_COSTS and transaction.provision_costs:
+ # Create new transaction for bank costs
+ cost_id = self.copy(
+ cr, uid, transaction.id,
+ dict(
+ type = bt.BANK_COSTS,
+ transaction = '%s-prov' % transaction.transaction,
+ transferred_amount = transaction.provision_costs,
+ remote_currency = transaction.provision_costs_currency,
+ message = transaction.provision_costs_description,
+ parent_id = transaction.id,
+ ), context)
+
+ injected.append(self.browse(cr, uid, cost_id, context))
+
+ # Remove bank costs from current transaction
+ # Note that this requires that the transferred_amount
+ # includes the bank costs and that the costs itself are
+ # signed correctly.
+ self.write(
+ cr, uid, transaction.id,
+ dict(
+ transferred_amount =
+ transaction.transferred_amount - transaction.provision_costs,
+ provision_costs = False,
+ provision_costs_currency = False,
+ provision_costs_description = False,
+ ), context=context)
+ # rebrowse the current record after writing
+ transaction=self.browse(cr, uid, transaction.id, context=context)
+ # Match full direct debit orders
+ if transaction.type == bt.DIRECT_DEBIT:
+ move_info = self._match_debit_order(
+ cr, uid, transaction, results['log'], context)
+ if transaction.type == bt.STORNO:
+ move_info = self._match_storno(
+ cr, uid, transaction, results['log'], context)
+ # Allow inclusion of generated bank invoices
+ if transaction.type == bt.BANK_COSTS:
+ lines = self._match_costs(
+ cr, uid, transaction, period_id, account_info,
+ results['log']
+ )
+ results['bank_costs_invoice_cnt'] += bool(lines)
+ for line in lines:
+ if not [x for x in move_lines if x.id == line.id]:
+ move_lines.append(line)
+ partner_ids = [account_info.bank_partner_id.id]
+ partner_banks = []
+ else:
+ # Link remote partner, import account when needed
+ partner_banks = get_bank_accounts(
+ self.pool, cr, uid, transaction.remote_account,
+ results['log'], fail=True
+ )
+ if partner_banks:
+ partner_ids = [x.partner_id.id for x in partner_banks]
+ elif transaction.remote_owner:
+ iban = sepa.IBAN(transaction.remote_account)
+ if iban.valid:
+ country_code = iban.countrycode
+ elif transaction.remote_owner_country_code:
+ country_code = transaction.remote_owner_country_code
+ # fallback on the import parsers country code
+ elif transaction.bank_country_code:
+ country_code = transaction.bank_country_code
+ elif company.partner_id and company.partner_id.country:
+ country_code = company.partner_id.country.code
+ else:
+ country_code = None
+ partner_id = get_or_create_partner(
+ self.pool, cr, uid, transaction.remote_owner,
+ transaction.remote_owner_address,
+ transaction.remote_owner_postalcode,
+ transaction.remote_owner_city,
+ country_code, results['log']
+ )
+ if transaction.remote_account:
+ partner_bank_id = create_bank_account(
+ self.pool, cr, uid, partner_id,
+ transaction.remote_account,
+ transaction.remote_owner,
+ transaction.remote_owner_address,
+ transaction.remote_owner_city,
+ country_code, results['log']
+ )
+ partner_banks = partner_bank_obj.browse(
+ cr, uid, [partner_bank_id]
+ )
+ else:
+ partner_bank_id = None
+ partner_banks = []
+ partner_ids = [partner_id]
+ else:
+ partner_ids = []
+ partner_banks = []
+
+ # Credit means payment... isn't it?
+ if (not move_info
+ and transaction.transferred_amount < 0 and payment_lines):
+ # Link open payment - if any
+ move_info = self._match_payment(
+ cr, uid, transaction,
+ payment_lines, partner_ids,
+ partner_banks, results['log'], linked_payments,
+ )
+
+ # Second guess, invoice -> may split transaction, so beware
+ if not move_info:
+ # Link invoice - if any. Although bank costs are not an
+ # invoice, automatic invoicing on bank costs will create
+ # these, and invoice matching still has to be done.
+
+ transaction, move_info, remainder = self._match_invoice(
+ cr, uid, transaction, move_lines, partner_ids,
+ partner_banks, results['log'], linked_invoices,
+ context=context)
+ if remainder:
+ injected.append(self.browse(cr, uid, remainder, context))
+
+ account_id = move_info and move_info.get('account_id', False)
+ if not account_id:
+ # Use the default settings, but allow individual partner
+ # settings to overrule this. Note that you need to change
+ # the internal type of these accounts to either 'payable'
+ # or 'receivable' to enable usage like this.
+ if transaction.transferred_amount < 0:
+ if len(partner_banks) == 1:
+ account_id = (
+ partner_banks[0].partner_id.property_account_payable and
+ partner_banks[0].partner_id.property_account_payable.id)
+ if len(partner_banks) != 1 or not account_id or account_id == def_pay_account_id:
+ account_id = (account_info.default_credit_account_id and
+ account_info.default_credit_account_id.id)
+ else:
+ if len(partner_banks) == 1:
+ account_id = (
+ partner_banks[0].partner_id.property_account_receivable and
+ partner_banks[0].partner_id.property_account_receivable.id)
+ if len(partner_banks) != 1 or not account_id or account_id == def_rec_account_id:
+ account_id = (account_info.default_debit_account_id and
+ account_info.default_debit_account_id.id)
+ values = {}
+ self_values = {}
+ if move_info:
+ results['trans_matched_cnt'] += 1
+ self_values['match_type'] = move_info['match_type']
+ self_values['payment_line_id'] = move_info.get('payment_line_id', False)
+ self_values['move_line_ids'] = [(6, 0, move_info.get('move_line_ids') or [])]
+ self_values['invoice_ids'] = [(6, 0, move_info.get('invoice_ids') or [])]
+ self_values['payment_order_ids'] = [(6, 0, move_info.get('payment_order_ids') or [])]
+ self_values['payment_order_id'] = (move_info.get('payment_order_ids', False) and
+ len(move_info['payment_order_ids']) == 1 and
+ move_info['payment_order_ids'][0]
+ )
+ self_values['move_line_id'] = (move_info.get('move_line_ids', False) and
+ len(move_info['move_line_ids']) == 1 and
+ move_info['move_line_ids'][0]
+ )
+ if move_info['match_type'] == 'invoice':
+ self_values['invoice_id'] = (move_info.get('invoice_ids', False) and
+ len(move_info['invoice_ids']) == 1 and
+ move_info['invoice_ids'][0]
+ )
+ values['partner_id'] = move_info['partner_id']
+ values['partner_bank_id'] = move_info['partner_bank_id']
+ values['type'] = move_info['type']
+ # values['match_type'] = move_info['match_type']
+ else:
+ values['partner_id'] = values['partner_bank_id'] = False
+ if not values['partner_id'] and partner_ids and len(partner_ids) == 1:
+ values['partner_id'] = partner_ids[0]
+ if (not values['partner_bank_id'] and partner_banks and
+ len(partner_banks) == 1):
+ values['partner_bank_id'] = partner_banks[0].id
+ if not transaction.statement_line_id:
+ values.update(dict(
+ name = '%s.%s' % (transaction.statement, transaction.transaction),
+ date = transaction.effective_date,
+ amount = transaction.transferred_amount,
+ statement_id = transaction.statement_id.id,
+ note = transaction.message,
+ ref = transaction.reference,
+ period_id = period_id,
+ currency = account_info.currency_id.id,
+ account_id = account_id,
+ import_transaction_id = transaction.id,
+ ))
+ statement_line_id = statement_line_obj.create(cr, uid, values, context)
+ results['trans_loaded_cnt'] += 1
+ self_values['statement_line_id'] = statement_line_id
+ else:
+ statement_line_obj.write(
+ cr, uid, transaction.statement_line_id.id, values, context)
+ self.write(cr, uid, transaction.id, self_values, context)
+ if not injected:
+ i += 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.
+ cr.execute(
+ "SELECT DISTINCT o.id "
+ "FROM payment_order o, payment_line l "
+ "WHERE o.state = 'sent' "
+ "AND o.id = l.order_id "
+ "AND o.id NOT IN ("
+ "SELECT DISTINCT order_id AS id "
+ "FROM payment_line "
+ "WHERE date_done IS NULL "
+ "AND id IN (%s)"
+ ")" % (','.join([str(x) for x in payment_line_ids]))
+ )
+ order_ids = [x[0] for x in cr.fetchall()]
+ if order_ids:
+ # Use workflow logics for the orders. Recode logic from
+ # account_payment, in order to increase efficiency.
+ payment_order_obj.set_done(cr, uid, order_ids,
+ {'state': 'done'}
+ )
+ wf_service = netsvc.LocalService('workflow')
+ for id in order_ids:
+ wf_service.trg_validate(
+ uid, 'payment.order', id, 'done', cr)
+
+ def _get_residual(self, cr, uid, ids, name, args, context=None):
+ """
+ Calculate the residual against the candidate reconciliation.
+ When
+
+ 55 debiteuren, 50 binnen: amount > 0, residual > 0
+ -55 crediteuren, -50 binnen: amount = -60 residual -55 - -50
+
+ - residual > 0 and transferred amount > 0, or
+ - residual < 0 and transferred amount < 0
+
+ the result is a partial reconciliation. In the other cases,
+ a new statement line can be split off.
+
+ We should give users the option to reconcile with writeoff
+ or partial reconciliation / new statement line
+ """
+
+ if not ids:
+ return {}
+ res = dict([(x, False) for x in ids])
+ move_line_obj = self.pool.get('account.move.line')
+ for transaction in self.browse(cr, uid, ids, context):
+ if (transaction.statement_line_id.state == 'draft'
+ and transaction.match_type in
+ [('invoice'), ('move'), ('manual')]
+ and transaction.move_line_id):
+ rec_moves = (
+ transaction.move_line_id.reconcile_id and
+ transaction.move_line_id.reconcile_id.line_id or
+ transaction.move_line_id.reconcile_partial_id and
+ transaction.move_line_id.reconcile_partial_id.line_partial_ids or
+ [transaction.move_line_id])
+ res[transaction.id] = (
+ move_line_obj.get_balance(cr, uid, [x.id for x in rec_moves])
+ - transaction.transferred_amount)
+ return res
+
+ def _get_match_multi(self, cr, uid, ids, name, args, context=None):
+ """
+ Indicate in the wizard that multiple matches have been found
+ and that the user has not yet made a choice between them.
+ """
+ if not ids:
+ return {}
+ res = dict([(x, False) for x in ids])
+ for transaction in self.browse(cr, uid, ids, context):
+ if transaction.match_type == 'move':
+ if transaction.move_line_ids and not transaction.move_line_id:
+ res[transaction.id] = True
+ elif transaction.match_type == 'invoice':
+ if transaction.invoice_ids and not transaction.invoice_id:
+ res[transaction.id] = True
+ elif transaction.match_type == 'payment_order':
+ if (transaction.payment_order_ids and not
+ transaction.payment_order_id):
+ res[transaction.id] = True
+ return res
+
+ def clear_and_write(self, cr, uid, ids, vals=None, context=None):
+ """
+ Write values in argument 'vals', but clear all match
+ related values first
+ """
+ write_vals = (dict([(x, False) for x in [
+ 'match_type',
+ 'move_line_id',
+ 'invoice_id',
+ 'manual_invoice_id',
+ 'manual_move_line_id',
+ 'payment_line_id',
+ ]] +
+ [(x, [(6, 0, [])]) for x in [
+ 'move_line_ids',
+ 'invoice_ids',
+ 'payment_order_ids',
+ ]]))
+ write_vals.update(vals or {})
+ return self.write(cr, uid, ids, write_vals, context=context)
+
+ column_map = {
+ # used in bank_import.py, converting non-osv transactions
+ 'statement_id': 'statement',
+ 'id': 'transaction'
+ }
+
+ _columns = {
+ # start mem_bank_transaction atributes
+ # see parsers/models.py
+ 'transaction': fields.char('transaction', size=16), # id
+ 'statement': fields.char('statement', size=16), # statement_id
+ 'type': fields.char('type', size=16),
+ 'reference': fields.char('reference', size=1024),
+ 'local_account': fields.char('local_account', size=24),
+ 'local_currency': fields.char('local_currency', size=16),
+ 'execution_date': fields.date('execution_date'),
+ 'effective_date': fields.date('effective_date'),
+ 'remote_account': fields.char('remote_account', size=24),
+ 'remote_currency': fields.char('remote_currency', size=16),
+ 'exchange_rate': fields.float('exchange_rate'),
+ 'transferred_amount': fields.float('transferred_amount'),
+ 'message': fields.char('message', size=1024),
+ 'remote_owner': fields.char('remote_owner', size=24),
+ 'remote_owner_address': fields.char('remote_owner_address', size=24),
+ 'remote_owner_city': fields.char('remote_owner_city', size=24),
+ 'remote_owner_postalcode': fields.char('remote_owner_postalcode', size=24),
+ 'remote_owner_country_code': fields.char('remote_owner_country_code', size=24),
+ 'remote_owner_custno': fields.char('remote_owner_custno', size=24),
+ 'remote_bank_bic': fields.char('remote_bank_bic', size=24),
+ 'remote_bank_bei': fields.char('remote_bank_bei', size=24),
+ 'remote_bank_ibei': fields.char('remote_bank_ibei', size=24),
+ 'remote_bank_eangl': fields.char('remote_bank_eangln', size=24),
+ 'remote_bank_chips_uid': fields.char('remote_bank_chips_uid', size=24),
+ 'remote_bank_duns': fields.char('remote_bank_duns', size=24),
+ 'remote_bank_tax_id': fields.char('remote_bank_tax_id', size=24),
+ 'provision_costs': fields.float('provision_costs', size=24),
+ 'provision_costs_currency': fields.char('provision_costs_currency', size=64),
+ 'provision_costs_description': fields.char('provision_costs_description', size=24),
+ 'error_message': fields.char('error_message', size=1024),
+ 'storno_retry': fields.boolean('storno_retry'),
+ # end of mem_bank_transaction_fields
+ 'bank_country_code': fields.char(
+ 'Bank country code', size=2,
+ help=("Fallback default country for new partner records, "
+ "as defined by the import parser"),
+ readonly=True,),
+ 'company_id': fields.many2one(
+ 'res.company', 'Company', required=True),
+ 'duplicate': fields.boolean('duplicate'),
+ 'statement_line_id': fields.many2one(
+ 'account.bank.statement.line', 'Statement line',
+ ondelete='CASCADE'),
+ 'statement_id': fields.many2one(
+ 'account.bank.statement', 'Statement'),
+ 'parent_id': fields.many2one(
+ 'banking.import.transaction', 'Split off from this transaction'),
+ # match fields
+ 'match_type': fields.selection(
+ [('manual', 'Manual'), ('move','Move'), ('invoice', 'Invoice'),
+ ('payment', 'Payment'), ('payment_order', 'Payment order'),
+ ('storno', 'Storno')],
+ 'Match type'),
+ 'match_multi': fields.function(
+ _get_match_multi, method=True, string='Multi match',
+ type='boolean'),
+ 'payment_order_ids': fields.many2many(
+ 'payment.order', 'banking_transaction_payment_order_rel',
+ 'order_id', 'transaction_id', 'Payment orders'),
+ 'payment_order_id': fields.many2one(
+ 'payment.order', 'Payment order to reconcile'),
+ 'move_line_ids': fields.many2many(
+ 'account.move.line', 'banking_transaction_move_line_rel',
+ 'move_line_id', 'transaction_id', 'Matching entries'),
+ 'move_line_id': fields.many2one(
+ 'account.move.line', 'Entry to reconcile'),
+ 'payment_line_id': fields.many2one('payment.line', 'Payment line'),
+ 'invoice_ids': fields.many2many(
+ 'account.invoice', 'banking_transaction_invoice_rel',
+ 'invoice_id', 'transaction_id', 'Matching invoices'),
+ 'invoice_id': fields.many2one(
+ 'account.invoice', 'Invoice to reconcile'),
+ 'payment_line_id': fields.many2one('payment.line', 'Payment line'),
+ 'residual': fields.function(
+ _get_residual, method=True, string='Residual', type='float'),
+ 'writeoff_account_id': fields.many2one(
+ 'account.account', 'Write-off account',
+ domain=[('type', '!=', 'view')]),
+ 'writeoff_journal_id': fields.many2one(
+ 'account.journal', 'Write-off journal'),
+ 'writeoff_move_line_id': fields.many2one(
+ 'account.move.line', 'Write off move line'),
+ }
+ _defaults = {
+ 'company_id': lambda s,cr,uid,c:
+ s.pool.get('res.company')._company_default_get(
+ cr, uid, 'bank.import.transaction', context=c),
+ }
+banking_import_transaction()
+
+class account_bank_statement_line(osv.osv):
+ _inherit = 'account.bank.statement.line'
+ _columns = {
+ 'import_transaction_id': fields.many2one(
+ 'banking.import.transaction',
+ 'Import transaction', readonly=True),
+ 'match_multi': fields.related(
+ 'import_transaction_id', 'match_multi', type='boolean',
+ string='Multi match', readonly=True),
+ 'residual': fields.related(
+ 'import_transaction_id', 'residual', type='float',
+ string='Residual'),
+ 'duplicate': fields.related(
+ 'import_transaction_id', 'duplicate', type='boolean',
+ string='Possible duplicate import', readonly=True),
+ 'match_type': fields.related(
+ 'import_transaction_id', 'match_type', type='selection',
+ selection=[('manual', 'Manual'), ('move','Move'),
+ ('invoice', 'Invoice'), ('payment', 'Payment'),
+ ('payment_order', 'Payment order'),
+ ('storno', 'Storno')],
+ string='Match type', readonly=True,),
+ 'residual': fields.related(
+ 'import_transaction_id', 'residual', type='float',
+ string='Residual', readonly=True,
+ ),
+ 'state': fields.selection(
+ [('draft', 'Draft'), ('confirmed', 'Confirmed')], 'State',
+ readonly=True, required=True),
+ 'move_id': fields.many2one(
+ 'account.move', 'Move', readonly=True,
+ help="The accounting move associated with this line"),
+ }
+
+ _defaults = {
+ 'state': 'draft',
+ }
+
+ def match_wizard(self, cr, uid, ids, context=None):
+ res = False
+ if ids:
+ if isinstance(ids, (int, float)):
+ ids = [ids]
+ if context is None:
+ context = {}
+ context['statement_line_id'] = ids[0]
+ wizard_obj = self.pool.get('banking.transaction.wizard')
+ res_id = wizard_obj.create(
+ cr, uid, {'statement_line_id': ids[0]}, context=context)
+ res = wizard_obj.create_act_window(cr, uid, res_id, context=context)
+ return res
+
+ def confirm(self, cr, uid, ids, context=None):
+ # TODO: a confirmed transaction should remove its reconciliation target
+ # from other transactions where it is one of multiple candidates or
+ # even the proposed reconciliation target.
+ statement_obj = self.pool.get('account.bank.statement')
+ obj_seq = self.pool.get('ir.sequence')
+ import_transaction_obj = self.pool.get('banking.import.transaction')
+
+ for st_line in self.browse(cr, uid, ids, context):
+ if st_line.state != 'draft':
+ continue
+ if st_line.duplicate:
+ raise osv.except_osv(
+ _('Bank transfer flagged as duplicate'),
+ _("You cannot confirm a bank transfer marked as a "
+ "duplicate (%s.%s)") %
+ (st_line.statement_id.name, st_line.name,))
+ if st_line.analytic_account_id:
+ if not st_line.statement_id.journal_id.analytic_journal_id:
+ raise osv.except_osv(
+ _('No Analytic Journal !'),
+ _("You have to define an analytic journal on the '%s' "
+ "journal!") % (st_line.statement_id.journal_id.name,))
+ if not st_line.amount:
+ continue
+ if st_line.import_transaction_id:
+ import_transaction_obj.reconcile(
+ cr, uid, st_line.import_transaction_id.id, context)
+
+ if not st_line.statement_id.name == '/':
+ st_number = st_line.statement_id.name
+ else:
+ if st_line.statement_id.journal_id.sequence_id:
+ c = {'fiscalyear_id':
+ st_line.statement_id.period_id.fiscalyear_id.id}
+ st_number = obj_seq.get_id(
+ cr, uid,
+ st_line.statement_id.journal_id.sequence_id.id,
+ context=c)
+ else:
+ st_number = obj_seq.get(cr, uid, 'account.bank.statement')
+ statement_obj.write(
+ cr, uid, [st_line.statement_id.id],
+ {'name': st_number}, context=context)
+
+ st_line_number = statement_obj.get_next_st_line_number(
+ cr, uid, st_number, st_line, context)
+ company_currency_id = st_line.statement_id.journal_id.company_id.currency_id.id
+ move_id = statement_obj.create_move_from_st_line(
+ cr, uid, st_line.id, company_currency_id,
+ st_line_number, context)
+ self.write(
+ cr, uid, st_line.id,
+ {'state': 'confirmed', 'move_id': move_id}, context)
+ return True
+
+ def cancel(self, cr, uid, ids, context=None):
+ if ids and isinstance(ids, (int, float)):
+ ids = [ids]
+ account_move_obj = self.pool.get('account.move')
+ import_transaction_obj = self.pool.get('banking.import.transaction')
+ transaction_cancel_ids = []
+ move_unlink_ids = []
+ set_draft_ids = []
+ # harvest ids for various actions
+ for st_line in self.browse(cr, uid, ids, context):
+ if st_line.state != 'confirmed':
+ continue
+ if st_line.statement_id.state != 'draft':
+ raise osv.except_osv(
+ _("Cannot cancel bank transaction"),
+ _("The bank statement that this transaction belongs to has "
+ "already been confirmed"))
+ if st_line.import_transaction_id:
+ transaction_cancel_ids.append(st_line.import_transaction_id.id)
+ if st_line.move_id:
+ move_unlink_ids.append(st_line.move_id.id)
+ else:
+ raise osv.except_osv(
+ _("Cannot cancel bank transaction"),
+ _("Cannot cancel this bank transaction. The information "
+ "needed to undo the accounting entries has not been "
+ "recorded"))
+ set_draft_ids.append(st_line.id)
+ # perform actions
+ import_transaction_obj.cancel(
+ cr, uid, transaction_cancel_ids, context=context)
+ account_move_obj.button_cancel(cr, uid, move_unlink_ids, context)
+ account_move_obj.unlink(cr, uid, move_unlink_ids, context)
+ self.write(
+ cr, uid, set_draft_ids, {'state': 'draft'}, context=context)
+ return True
+account_bank_statement_line()
+
+class account_bank_statement(osv.osv):
+ _inherit = 'account.bank.statement'
+
+ def _end_balance(self, cursor, user, ids, name, attr, context=None):
+ """
+ This method taken from account/account_bank_statement.py and
+ altered to take the statement line subflow into account
+ """
+
+ res_currency_obj = self.pool.get('res.currency')
+ res_users_obj = self.pool.get('res.users')
+ res = {}
+
+ company_currency_id = res_users_obj.browse(cursor, user, user,
+ context=context).company_id.currency_id.id
+
+ statements = self.browse(cursor, user, ids, context=context)
+ for statement in statements:
+ res[statement.id] = statement.balance_start
+ currency_id = statement.currency.id
+ for line in statement.move_line_ids:
+ if line.debit > 0:
+ if line.account_id.id == \
+ statement.journal_id.default_debit_account_id.id:
+ res[statement.id] += res_currency_obj.compute(cursor,
+ user, company_currency_id, currency_id,
+ line.debit, context=context)
+ else:
+ if line.account_id.id == \
+ statement.journal_id.default_credit_account_id.id:
+ res[statement.id] -= res_currency_obj.compute(cursor,
+ user, company_currency_id, currency_id,
+ line.credit, context=context)
+ if statement.state == 'draft':
+ for line in statement.line_ids:
+ ### start modifications banking-addons ###
+ # res[statement.id] += line.amount
+ if line.state == 'draft':
+ res[statement.id] += line.amount
+ ### end modifications banking-addons ###
+
+ for r in res:
+ res[r] = round(res[r], 2)
+ return res
+
+ def button_confirm_bank(self, cr, uid, ids, context=None):
+ """ Inject the statement line workflow here """
+ if context is None:
+ context = {}
+ line_obj = self.pool.get('account.bank.statement.line')
+ for st in self.browse(cr, uid, ids, context=context):
+ j_type = st.journal_id.type
+ if not self.check_status_condition(cr, uid, st.state, journal_type=j_type):
+ continue
+
+ self.balance_check(cr, uid, st.id, journal_type=j_type, context=context)
+ if (not st.journal_id.default_credit_account_id) \
+ or (not st.journal_id.default_debit_account_id):
+ raise osv.except_osv(_('Configuration Error !'),
+ _('Please verify that an account is defined in the journal.'))
+
+ # protect against misguided manual changes
+ 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.'))
+
+ line_obj.confirm(cr, uid, [line.id for line in st.line_ids], context)
+ self.log(cr, uid, st.id, _('Statement %s is confirmed, journal '
+ 'items are created.') % (st.name,))
+ return self.write(cr, uid, ids, {'state':'confirm'}, context=context)
+
+ def button_cancel(self, cr, uid, ids, context=None):
+ """
+ Do nothing but write the state. Delegate all actions to the statement
+ line workflow instead.
+ """
+ self.write(cr, uid, ids, {'state':'draft'}, context=context)
+
+ _columns = {
+ # override this field *only* to replace the
+ # function method with the one from this module.
+ # Note that it is defined twice, both in
+ # account/account_bank_statement.py (without 'store') and
+ # account/account_cash_statement.py (with store=True)
+
+ 'balance_end': fields.function(
+ _end_balance, method=True, store=True, string='Balance'),
+ }
+
+account_bank_statement()
=== added file 'account_banking/i18n/nl.po'
--- account_banking/i18n/nl.po 1970-01-01 00:00:00 +0000
+++ account_banking/i18n/nl.po 2012-01-17 10:38:36 +0000
@@ -0,0 +1,1773 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+# * account_banking
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 6.0.3\n"
+"Report-Msgid-Bugs-To: stefan@xxxxxxxx\n"
+"POT-Creation-Date: 2011-12-26 08:06+0000\n"
+"PO-Revision-Date: 2011-12-26 08:06+0000\n"
+"Last-Translator: <stefan@xxxxxxxx>\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
+#: field:account.bank.statement.line,reconcile_id:0
+msgid "Reconciliation"
+msgstr "Aflettering"
+
+#. module: account_banking
+#: view:banking.transaction.wizard:0
+msgid "Remove duplicate flag"
+msgstr "Verwijder kenmerk 'duplicaat'"
+
+#. module: account_banking
+#: field:banking.import.transaction,remote_bank_eangl:0
+msgid "remote_bank_eangln"
+msgstr "remote_bank_eangln"
+
+#. module: account_banking
+#: field:banking.transaction.wizard,move_line_ids:0
+msgid "Entry lines"
+msgstr "Boekingsregels"
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/bank_import.py:310
+#, python-format
+msgid "Number of bank costs invoices created"
+msgstr "Aantal bankkosten-facturen aangemaakt"
+
+#. module: account_banking
+#: code:addons/account_banking/account_banking.py:1017
+#: code:addons/account_banking/banking_import_transaction.py:632
+#: code:addons/account_banking/banking_import_transaction.py:644
+#: code:addons/account_banking/banking_import_transaction.py:648
+#, python-format
+msgid "Cannot unreconcile"
+msgstr "Kan niet afletteren"
+
+#. module: account_banking
+#: selection:banking.import.line,transaction_type:0
+msgid "Unknown"
+msgstr "Onbekend"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:950
+#, python-format
+msgid "Cannot check for duplicate. I can't find myself."
+msgstr "Kan niet controleren op duplicaten, ik kan de eigen mutatie niet terugvinden."
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/bank_import.py:298
+#, python-format
+msgid "Number of errors found"
+msgstr "Aantal gevonden fouten"
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/bank_import.py:229
+#, python-format
+msgid "Statement %(statement_id)s for account %(bank_account)s uses different currency than the defined bank journal."
+msgstr "Afschrift %(statement_id)s voor rekening %(bank_account)s gebruikt een andere valuta dan het ingestelde bankboek."
+
+#. module: account_banking
+#: view:account.bank.statement:0
+#: view:account.bank.statement.line:0
+msgid "Cancel transaction"
+msgstr "Transactie annuleren"
+
+#. module: account_banking
+#: view:account.banking.bank.import:0
+msgid "Select the processing details:"
+msgstr "Selecteer de verwerkingsdetails:"
+
+#. module: account_banking
+#: view:account.bank.statement.line:0
+msgid "Group By..."
+msgstr "Groepeer op..."
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/bank_import.py:198
+#, python-format
+msgid "Statements found for unknown account %(bank_account)s"
+msgstr "Afschriften gevonden voor onbekende bankrekening %(bank_account)s"
+
+#. module: account_banking
+#: code:addons/account_banking/account_banking.py:1320
+#, python-format
+msgid "Invalid format"
+msgstr "Ongeldig formaat"
+
+#. module: account_banking
+#: field:banking.import.line,banking_import_id:0
+msgid "Bank import"
+msgstr "Bankimport"
+
+#. module: account_banking
+#: field:banking.import.line,statement_id:0
+#: field:banking.import.transaction,statement_id:0
+msgid "Statement"
+msgstr "Afschrift"
+
+#. module: account_banking
+#: view:account.bank.statement.line:0
+msgid "Statement lines"
+msgstr "Afschriftregels"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:605
+#, python-format
+msgid "Reconcile payment order not implemented"
+msgstr "Afletteren betaalopdracht nog niet geïmplementeerd"
+
+#. module: account_banking
+#: selection:banking.import.line,type:0
+msgid "Supplier"
+msgstr "Leverancier"
+
+#. module: account_banking
+#: field:payment.line,date_done:0
+msgid "Date Confirmed"
+msgstr "Datum bevestigd"
+
+#. module: account_banking
+#: field:banking.import.transaction,remote_bank_bic:0
+msgid "remote_bank_bic"
+msgstr "remote_bank_bic"
+
+#. module: account_banking
+#: field:banking.transaction.wizard,manual_invoice_id:0
+msgid "Match this invoice"
+msgstr "Match deze factuur"
+
+#. module: account_banking
+#: field:banking.import.transaction,remote_bank_ibei:0
+msgid "remote_bank_ibei"
+msgstr "remote_bank_ibei"
+
+#. module: account_banking
+#: field:account.banking.account.settings,bank_partner_id:0
+msgid "Bank Partner"
+msgstr "Relatie bank"
+
+#. 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
+#: field:account.banking.bank.import,file:0
+msgid "Statements File"
+msgstr "Bankafschriftbestand"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:601
+#, python-format
+msgid "Cannot reconcile: no direct debit order"
+msgstr "Kan niet afletteren: geen incasso-opdracht"
+
+#. module: account_banking
+#: code:addons/account_banking/account_banking.py:1321
+#, python-format
+msgid "The account number has the wrong format for %(country)s"
+msgstr "Het rekeningnummer heeft het verkeerde formaat voor %(country)s"
+
+#. module: account_banking
+#: field:account.banking.bank.import,import_id:0
+msgid "Import File"
+msgstr "Importbestand"
+
+#. module: account_banking
+#: field:banking.import.transaction,move_line_id:0
+msgid "Entry to reconcile"
+msgstr "Boeking voor afletteren"
+
+#. module: account_banking
+#: field:account.banking.account.settings,company_id:0
+#: field:account.banking.bank.import,company:0
+#: field:account.banking.imported.file,company_id:0
+#: field:banking.import.transaction,company_id:0
+msgid "Company"
+msgstr "Bedrijf"
+
+#. module: account_banking
+#: selection:account.bank.statement.line,match_type:0
+#: field:banking.import.line,payment_order_id:0
+#: selection:banking.import.transaction,match_type:0
+msgid "Payment order"
+msgstr "Betaalopdracht"
+
+#. module: account_banking
+#: field:banking.import.transaction,parent_id:0
+msgid "Split off from this transaction"
+msgstr "Afsplitsen"
+
+#. module: account_banking
+#: field:account.bank.statement.line,residual:0
+#: field:banking.import.transaction,residual:0
+#: field:banking.transaction.wizard,residual:0
+msgid "Residual"
+msgstr "Restbedrag"
+
+#. module: account_banking
+#: field:account.bank.statement.line,invoice_id:0
+msgid "Linked Invoice"
+msgstr "Gerelateerde factuur"
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/banktools.py:107
+#, python-format
+msgid "Bank account %(account_no)s was not found in the database"
+msgstr "Bankrekening %(account_no)s niet gevonden in de database"
+
+#. module: account_banking
+#: selection:account.banking.bank.import,state:0
+msgid "init"
+msgstr "init"
+
+#. module: account_banking
+#: field:banking.import.transaction,transferred_amount:0
+msgid "transferred_amount"
+msgstr "transferred_amount"
+
+#. module: account_banking
+#: model:ir.actions.act_window,name:account_banking.action_bank_statement_line_tree
+#: model:ir.ui.menu,name:account_banking.menu_bank_statement_line_tree
+msgid "Bank Transactions"
+msgstr "Bankmutaties"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:1676
+#, python-format
+msgid "You cannot confirm a bank transfer marked as a duplicate (%s.%s)"
+msgstr "Een bankmutatie gemarkeerd als duplicaat kan niet worden bevestigd (%s.%s)"
+
+#. module: account_banking
+#: field:banking.import.line,statement_line_id:0
+msgid "Resulting statement line"
+msgstr "Bankafschriftregel"
+
+#. module: account_banking
+#: field:banking.import.transaction,invoice_ids:0
+#: field:banking.transaction.wizard,invoice_ids:0
+msgid "Matching invoices"
+msgstr "Gematchte facturen"
+
+#. module: account_banking
+#: code:addons/account_banking/account_banking.py:1435
+#, python-format
+msgid "Free Reference"
+msgstr "Vrije referentie"
+
+#. module: account_banking
+#: field:banking.import.line,reconcile_id:0
+msgid "Reconciliaton"
+msgstr "Aflettering"
+
+#. module: account_banking
+#: field:banking.import.transaction,execution_date:0
+msgid "execution_date"
+msgstr "execution_date"
+
+#. module: account_banking
+#: field:banking.import.line,account_id:0
+msgid "Account"
+msgstr "Grootboekrekening"
+
+#. module: account_banking
+#: view:banking.transaction.wizard:0
+msgid "Current match"
+msgstr "Huidige match"
+
+#. module: account_banking
+#: field:account.banking.account.settings,invoice_journal_id:0
+msgid "Costs Journal"
+msgstr "Kostendagboek"
+
+#. module: account_banking
+#: field:banking.import.transaction,remote_currency:0
+msgid "remote_currency"
+msgstr "remote_currency"
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/bank_import.py:300
+#, python-format
+msgid "Number of statements skipped due to errors"
+msgstr "Aantal afschriften overgeslagen door fouten"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:949
+#, python-format
+msgid "Cannot check for duplicate"
+msgstr "Kan niet controleren op duplicaten"
+
+#. module: account_banking
+#: code:addons/account_banking/account_banking.py:1351
+#, python-format
+msgid "Invalid IBAN account number!"
+msgstr "Ongeldig IBAN-rekeningnummer"
+
+#. module: account_banking
+#: constraint:account.move.line:0
+msgid "You can not create move line on closed account."
+msgstr "U kunt geen boekingsregel creëren op een gesloten rekening"
+
+#. module: account_banking
+#: field:banking.import.line,note:0
+msgid "Notes"
+msgstr "Notities"
+
+#. module: account_banking
+#: selection:banking.import.line,transaction_type:0
+msgid "Canceled debit order"
+msgstr "Geannuleerde incasso-opdracht"
+
+#. module: account_banking
+#: field:banking.import.transaction,writeoff_journal_id:0
+#: field:banking.transaction.wizard,writeoff_journal_id:0
+msgid "Write-off journal"
+msgstr "Dagboek afschrijvingen"
+
+#. module: account_banking
+#: view:account.banking.account.settings:0
+msgid "Default Import Settings for Bank Account"
+msgstr "Standaardinstellingen bankrekening voor import"
+
+#. module: account_banking
+#: field:banking.import.line,amount:0
+#: field:banking.transaction.wizard,amount:0
+msgid "Amount"
+msgstr "Bedrag"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:704
+#, python-format
+msgid "Line id not found"
+msgstr "Regel id niet gevonden"
+
+#. module: account_banking
+#: field:account.bank.statement.line,match_type:0
+#: field:banking.import.transaction,match_type:0
+#: field:banking.transaction.wizard,match_type:0
+msgid "Match type"
+msgstr "Matchtype"
+
+#. module: account_banking
+#: help:banking.import.transaction,bank_country_code:0
+msgid "Fallback default country for new partner records, as defined by the import parser"
+msgstr "Achtervang-standaardland voor nieuwe relaties, zoals ingegeven door de invoerparser"
+
+#. module: account_banking
+#: sql_constraint:account.move.line:0
+msgid "Wrong credit or debit value in accounting entry !"
+msgstr "Verkeerde debet of credit waarde in boekingsregel!"
+
+#. module: account_banking
+#: field:banking.import.transaction,remote_bank_chips_uid:0
+msgid "remote_bank_chips_uid"
+msgstr "remote_bank_chips_uid"
+
+#. module: account_banking
+#: field:banking.import.transaction,writeoff_account_id:0
+#: field:banking.transaction.wizard,writeoff_account_id:0
+msgid "Write-off account"
+msgstr "Grootboekrekening afschrijven"
+
+#. module: account_banking
+#: selection:payment.line,export_state:0
+msgid "Cancelled"
+msgstr "Geannuleerd"
+
+#. module: account_banking
+#: view:account.banking.bank.import:0
+#: view:account.banking.imported.file:0
+#: field:account.banking.imported.file,statement_ids:0
+msgid "Statements"
+msgstr "Afschriften"
+
+#. module: account_banking
+#: field:banking.transaction.wizard,payment_line_id:0
+msgid "Matching payment or storno"
+msgstr "Gevonden betaling of storno"
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/bank_import.py:128
+#, 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
+#: model:ir.model,name:account_banking.model_account_bank_statement_line
+msgid "Bank Statement Line"
+msgstr "Bankafschriftregel"
+
+#. module: account_banking
+#: field:account.bank.statement.line,duplicate:0
+msgid "Possible duplicate import"
+msgstr "Mogelijke dubbele import"
+
+#. module: account_banking
+#: field:banking.import.line,ref:0
+msgid "Reference"
+msgstr "Referentie"
+
+#. module: account_banking
+#: code:addons/account_banking/account_banking.py:444
+#, python-format
+msgid "Journal Item \"%s\" is not valid"
+msgstr "Journaalpost \"%s\" is niet geldig"
+
+#. module: account_banking
+#: field:account.banking.account.settings,default_debit_account_id:0
+msgid "Default debit account"
+msgstr "Standaard debetrekening"
+
+#. module: account_banking
+#: selection:account.bank.statement.line,match_type:0
+#: field:account.bank.statement.line,move_id:0
+#: selection:banking.import.transaction,match_type:0
+msgid "Move"
+msgstr "Boeking"
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/banktools.py:307
+#: code:addons/account_banking/wizard/banktools.py:397
+#, python-format
+msgid "Unknown Bank"
+msgstr "Onbekende bank"
+
+#. module: account_banking
+#: selection:banking.import.line,transaction_type:0
+msgid "Invoice payment"
+msgstr "Factuurbetaling"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:1673
+#, python-format
+msgid "Bank transfer flagged as duplicate"
+msgstr "Bankmutatie is gemarkeerd als duplicaat"
+
+#. module: account_banking
+#: field:banking.import.transaction,writeoff_move_line_id:0
+msgid "Write off move line"
+msgstr "Boekingsregel afschrijving"
+
+#. module: account_banking
+#: field:banking.transaction.wizard,duplicate:0
+msgid "Flagged as duplicate"
+msgstr "Duplicaat gesignaleerd"
+
+#. module: account_banking
+#: view:banking.transaction.wizard:0
+#: model:ir.model,name:account_banking.model_banking_transaction_wizard
+msgid "Match transaction"
+msgstr "Match deze mutatie"
+
+#. module: account_banking
+#: sql_constraint:payment.line:0
+msgid "The payment line name must be unique!"
+msgstr "De betaalregelnaam moet uniek zijn!"
+
+#. module: account_banking
+#: code:addons/account_banking/account_banking.py:1309
+#, python-format
+msgid "The account number appears to be invalid for %(country)s"
+msgstr "Het bankrekeningnummer lijkt ongeldig te zijn voor %(country)s"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:633
+#, python-format
+msgid "Cannot unreconcile: this operation is not yet supported for match type 'payment'"
+msgstr "Kan niet afletteren: deze bewerking wordt noge niet ondersteund voor matches van het type 'betaling'"
+
+#. module: account_banking
+#: field:banking.import.line,partner_id:0
+msgid "Partner"
+msgstr "Relatie"
+
+#. module: account_banking
+#: field:account.banking.imported.file,date:0
+msgid "Import Date"
+msgstr "Importdatum"
+
+#. module: account_banking
+#: field:payment.mode.type,suitable_bank_types:0
+msgid "Suitable bank types"
+msgstr "Geschikte banktypen"
+
+#. module: account_banking
+#: view:banking.transaction.wizard:0
+msgid "Select"
+msgstr "Selecteer"
+
+#. module: account_banking
+#: model:ir.model,name:account_banking.model_account_banking_bank_import
+msgid "account.banking.bank.import"
+msgstr "account.banking.bank.import"
+
+#. module: account_banking
+#: field:banking.import.transaction,remote_bank_bei:0
+msgid "remote_bank_bei"
+msgstr "remote_bank_bei"
+
+#. module: account_banking
+#: selection:account.bank.statement.line,state:0
+#: selection:payment.line,export_state:0
+msgid "Confirmed"
+msgstr "Bevestigd"
+
+#. module: account_banking
+#: model:ir.actions.act_window,name:account_banking.action_account_banking_res_partner_banks
+#: model:ir.model,name:account_banking.model_res_partner_bank
+#: model:ir.ui.menu,name:account_banking.menu_action_account_banking_bank_accounts
+msgid "Bank Accounts"
+msgstr "Bankrekeningen"
+
+#. module: account_banking
+#: help:account.bank.statement.line,move_id:0
+msgid "The accounting move associated with this line"
+msgstr "Boeking nav. deze regel"
+
+#. module: account_banking
+#: view:account.banking.account.settings:0
+msgid "Default Accounts for Unknown Movements"
+msgstr "Standaard grootboekrekening voor onbekende boekingen"
+
+#. module: account_banking
+#: view:account.banking.bank.import:0
+msgid "Confirm"
+msgstr "Bevestig"
+
+#. module: account_banking
+#: field:account.banking.account.settings,default_credit_account_id:0
+msgid "Default credit account"
+msgstr "Standaard creditrekening"
+
+#. module: account_banking
+#: field:account.bank.statement.line,period_id:0
+#: field:banking.import.line,period_id:0
+msgid "Period"
+msgstr "Periode"
+
+#. module: account_banking
+#: field:banking.import.line,transaction_type:0
+msgid "Transaction type"
+msgstr "Mutatietype"
+
+#. module: account_banking
+#: field:account.bank.statement.line,state:0
+#: field:account.banking.bank.import,state:0
+#: field:account.banking.imported.file,state:0
+#: field:payment.line,export_state:0
+msgid "State"
+msgstr "Status"
+
+#. module: account_banking
+#: field:account.bank.statement.line,trans:0
+msgid "Bank Transaction ID"
+msgstr "Bankmutatie ID"
+
+#. module: account_banking
+#: model:ir.model,name:account_banking.model_payment_mode
+msgid "Payment Mode"
+msgstr "Betaalmodus"
+
+#. module: account_banking
+#: view:banking.transaction.wizard:0
+msgid "Match again"
+msgstr "Match nogmaals"
+
+#. module: account_banking
+#: help:account.banking.account.settings,invoice_journal_id:0
+msgid "This is the journal used to create invoices for bank costs."
+msgstr "Het dagboek om facturen aan te maken voor bankkosten."
+
+#. module: account_banking
+#: selection:banking.import.line,type:0
+msgid "General"
+msgstr "Algemeen"
+
+#. module: account_banking
+#: field:banking.import.line,type:0
+msgid "Type"
+msgstr "Type"
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/bank_import.py:251
+#, python-format
+msgid "Statement %(id)s known - skipped"
+msgstr "Afschrift %(id)s al bekend - overgeslagen"
+
+#. module: account_banking
+#: view:banking.transaction.wizard:0
+msgid "If the amount exceeds the match, you must set a write-off account and journal for the residual of this reconciliation. If the amount is smaller than the match, this is optional. If you do not set a write-off account in this case, the result will be a partial reconciliation."
+msgstr "Als het bedrag van de mutatie de gevonden match overschrijdt, moet er een grootboekrekening en een dagboek worden opgegeven om het restbedrag van de aflettering af te schrijven. Dit is optioneel als het bedrag van de mutatie kleiner is dan de gevonden match. Als u in dit geval geen afschrijving mogelijk maakt, zal het systeem een gedeeltelijke aflettering aanmaken."
+
+#. module: account_banking
+#: field:banking.transaction.wizard,move_line_id:0
+msgid "Entry line"
+msgstr "Boekingsregel"
+
+#. module: account_banking
+#: selection:banking.import.line,transaction_type:0
+msgid "Payment from a payment order"
+msgstr "Betaling uit een betaalopdracht"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:1732
+#: code:addons/account_banking/banking_import_transaction.py:1741
+#, python-format
+msgid "Cannot cancel bank transaction"
+msgstr "Kan de bankmutatie niet annuleren"
+
+#. module: account_banking
+#: code:addons/account_banking/account_banking.py:443
+#: code:addons/account_banking/banking_import_transaction.py:1818
+#, python-format
+msgid "Error !"
+msgstr "Fout !"
+
+#. module: account_banking
+#: code:addons/account_banking/account_banking.py:1308
+#, python-format
+msgid "Invalid data"
+msgstr "Ongeldige gegevens"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:684
+#: code:addons/account_banking/banking_import_transaction.py:688
+#: code:addons/account_banking/banking_import_transaction.py:703
+#, python-format
+msgid "Cannot cancel link with storno"
+msgstr "Kan de relatie met de storno niet annuleren"
+
+#. module: account_banking
+#: selection:account.banking.bank.import,state:0
+msgid "review"
+msgstr "review"
+
+#. module: account_banking
+#: selection:payment.line,export_state:0
+msgid "Rejected"
+msgstr "Verworpen"
+
+#. 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 bankrekeningen voor import"
+
+#. module: account_banking
+#: model:ir.actions.act_window,name:account_banking.act_account_banking_import_wizard
+#: model:ir.actions.act_window,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 bankafschriftbestand"
+
+#. module: account_banking
+#: help:account.banking.bank.import,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 bankafschriftbestand dat geïmporteerd dient te worden. De verschillende bankformaten gaan verschillend om met de situatie waarin hetzelfde bestand meerdere keren wordt geïmporteerd.\n"
+"\n"
+"Het is daarom in ieder geval aan te raden om de bankafschriften altijd in hetzelfde formaat te importeren."
+
+#. module: account_banking
+#: view:account.bank.statement:0
+#: view:account.bank.statement.line:0
+#: view:banking.transaction.wizard:0
+msgid "Match"
+msgstr "Match"
+
+#. module: account_banking
+#: field:banking.import.transaction,payment_line_id:0
+msgid "Payment line"
+msgstr "Betaling"
+
+#. module: account_banking
+#: field:banking.import.transaction,payment_order_id:0
+#: field:banking.transaction.wizard,payment_order_id:0
+msgid "Payment order to reconcile"
+msgstr "Betaalopdracht ter aflettering"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:1742
+#, python-format
+msgid "Cannot cancel this bank transaction. The information needed to undo the accounting entries has not been recorded"
+msgstr "Kan de bankmutatie niet annuleren. De benodigde informatie om de boekingen teniet te doen is niet beschikbaar"
+
+#. module: account_banking
+#: view:banking.transaction.wizard:0
+msgid "Duplicate flag"
+msgstr "Duplicaatkenmerk"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:461
+#, python-format
+msgid "Entry is already reconciled"
+msgstr "Boeking is al afgeletterd"
+
+#. module: account_banking
+#: view:account.banking.bank.import:0
+msgid "Transaction"
+msgstr "Mutatie"
+
+#. module: account_banking
+#: code:addons/account_banking/account_banking.py:1282
+#, python-format
+msgid "Insufficient data to select online conversion database"
+msgstr "Onvoldoende gegevens om een online conversiebestand te selecteren"
+
+#. module: account_banking
+#: field:account.banking.bank.import,statement_ids:0
+#: view:account.banking.imported.file:0
+msgid "Imported Bank Statements"
+msgstr "Geïmporteerde bankafschriften"
+
+#. module: account_banking
+#: selection:payment.mode.type,payment_order_type:0
+#: selection:payment.order,payment_order_type:0
+msgid "Direct debit"
+msgstr "Incasso-opdracht"
+
+#. module: account_banking
+#: model:ir.model,name:account_banking.model_banking_import_transaction
+msgid "Bank import transaction"
+msgstr "Geïmporteerde bankmutatie"
+
+#. module: account_banking
+#: model:ir.module.module,description:account_banking.module_meta_information
+msgid "\n"
+" Module to do banking.\n"
+"\n"
+" Note: This module is depending on BeautifulSoup.\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"
+" + The SWIFT database is consulted for bank information.\n"
+"\n"
+" * Adds dropin extensible import facility for bank communication in:\n"
+" - Drop-in input parser development.\n"
+" - MultiBank (NL) format transaction files available as\n"
+" account_banking_nl_multibank,\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"
+"\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 "\n"
+" Module to do banking.\n"
+"\n"
+" Note: This module is depending on BeautifulSoup.\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"
+" + The SWIFT database is consulted for bank information.\n"
+"\n"
+" * Adds dropin extensible import facility for bank communication in:\n"
+" - Drop-in input parser development.\n"
+" - MultiBank (NL) format transaction files available as\n"
+" account_banking_nl_multibank,\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"
+"\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"
+" "
+
+#. module: account_banking
+#: selection:account.banking.bank.import,state:0
+msgid "error"
+msgstr "error"
+
+#. module: account_banking
+#: selection:account.bank.statement.line,match_type:0
+#: selection:banking.import.transaction,match_type:0
+msgid "Manual"
+msgstr "Handmatig"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:1733
+#, python-format
+msgid "The bank statement that this transaction belongs to has already been confirmed"
+msgstr "Het bankafschrift waar deze mutatie toe behoort is al bevestigd"
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/bank_import.py:127
+#: code:addons/account_banking/wizard/bank_import.py:141
+#, python-format
+msgid "ERROR!"
+msgstr "FOUT!"
+
+#. module: account_banking
+#: view:account.bank.statement.line:0
+#: field:account.bank.statement.line,state:0
+#: field:account.banking.bank.import,state:0
+#: field:account.banking.imported.file,state:0
+#: field:payment.line,export_state:0
+msgid "State"
+msgstr "Staat"
+
+#. module: account_banking
+#: view:account.banking.bank.import:0
+msgid "Import Bank Transactions File"
+msgstr "Bankmutatiebestand import"
+
+#. module: account_banking
+#: view:payment.order:0
+msgid "Make Payments"
+msgstr "Maak betalingen"
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/banktools.py:206
+#, 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
+#: code:addons/account_banking/account_banking.py:1414
+#, python-format
+msgid "My reference"
+msgstr "Mijn referentie"
+
+#. module: account_banking
+#: field:banking.import.transaction,statement_line_id:0
+#: field:banking.transaction.wizard,statement_line_id:0
+msgid "Statement line"
+msgstr "Bankafschriftregel"
+
+#. module: account_banking
+#: field:banking.import.transaction,storno_retry:0
+msgid "storno_retry"
+msgstr "storno_retry"
+
+#. module: account_banking
+#: view:banking.transaction.wizard:0
+msgid "Residual write-off"
+msgstr "Afschrijven restbedrag"
+
+#. module: account_banking
+#: field:banking.import.transaction,effective_date:0
+msgid "effective_date"
+msgstr "effective_date"
+
+#. module: account_banking
+#: view:account.bank.statement.line:0
+msgid "Search Bank Transactions "
+msgstr "Zoek bankmutaties"
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/banktools.py:179
+#, 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:payment.order,date_sent:0
+msgid "Send date"
+msgstr "Datum verstuurd"
+
+#. module: account_banking
+#: model:ir.model,name:account_banking.model_payment_mode_type
+msgid "Payment Mode Type"
+msgstr "Type betaalmodus"
+
+#. module: account_banking
+#: field:banking.import.transaction,remote_bank_tax_id:0
+msgid "remote_bank_tax_id"
+msgstr "remote_bank_tax_id"
+
+#. module: account_banking
+#: view:account.banking.bank.import:0
+#: field:account.banking.bank.import,line_ids:0
+msgid "Transactions"
+msgstr "Mutaties"
+
+#. module: account_banking
+#: field:account.bank.statement.line,currency:0
+#: field:banking.import.line,currency:0
+msgid "Currency"
+msgstr "Valuta"
+
+#. module: account_banking
+#: field:banking.import.transaction,bank_country_code:0
+msgid "Bank country code"
+msgstr "Bank country code"
+
+#. module: account_banking
+#: help:payment.mode.type,ir_model_id:0
+msgid "Select the Payment Wizard for payments of this type. Leave empty for manual processing"
+msgstr "Selecteer de wizard voor het verwerken van betalingen van dit type. Laat leeg voor handmatige verwerking."
+
+#. module: account_banking
+#: field:payment.line,msg:0
+msgid "Message"
+msgstr "Boodschap"
+
+#. module: account_banking
+#: field:banking.transaction.wizard,match_multi:0
+msgid "Multiple matches"
+msgstr "Meerdere matches"
+
+#. module: account_banking
+#: view:account.banking.bank.import:0
+#: field:account.banking.bank.import,log:0
+msgid "Log"
+msgstr "Log"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:553
+#, python-format
+msgid "Cannot link transaction %s with accounting entry"
+msgstr "Kan mutatie %s niet koppelen aan een journaalboeking"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:1177
+#, python-format
+msgid "transaction %(statement_id)s.%(transaction_id)s for account %(bank_account)s uses different currency than the defined bank journal."
+msgstr "Mutatie %(statement_id)s.%(transaction_id)s van bankrekening %(bank_account)s gebruikt een andere valuta dan het ingestelde bankboek."
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:580
+#, python-format
+msgid "Cannot link with storno"
+msgstr "Kan niet koppelen met een storno"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:543
+#, python-format
+msgid "Cannot link transaction %s with invoice"
+msgstr "Kan mutatie %s niet koppelen aan een factuur"
+
+#. module: account_banking
+#: selection:account.banking.imported.file,state:0
+msgid "Review"
+msgstr "Herzien"
+
+#. module: account_banking
+#: field:banking.transaction.wizard,manual_move_line_id:0
+msgid "Or match this entry"
+msgstr "Of koppel deze boekingsregel"
+
+#. module: account_banking
+#: help:payment.mode.type,name:0
+msgid "Payment Type"
+msgstr "Betaaltype"
+
+#. module: account_banking
+#: code:addons/account_banking/account_banking.py:1281
+#, python-format
+msgid "Insufficient data"
+msgstr "Onvoldoende gegevens"
+
+#. module: account_banking
+#: field:account.banking.imported.file,file:0
+msgid "Raw Data"
+msgstr "Ruwe data"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:1813
+#, python-format
+msgid "Please verify that an account is defined in the journal."
+msgstr "Er is een standaardrekening niet ingesteld op het dagboek."
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:649
+#, python-format
+msgid "Unreconcile payment order not implemented"
+msgstr "Unreconcile payment order not implemented"
+
+#. 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 grootboekrekening waarop een onverwachte betaling kan worden geboekt, bijvoorbeeld in het geval van eeen klant die vooruitbetaalt of als er geen overeenkomende factuur kan worden gevonden in het systeem. Merk op dat er voor het bevestigen van de boekingen nog wijzigingen kunnen worden aangebracht."
+
+#. module: account_banking
+#: field:payment.mode.type,payment_order_type:0
+#: field:payment.order,payment_order_type:0
+msgid "Payment order type"
+msgstr "Type betaalopdracht"
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/bank_import.py:296
+#, python-format
+msgid "Total number of transactions"
+msgstr "Totaal aantal transacties"
+
+#. module: account_banking
+#: field:banking.import.transaction,exchange_rate:0
+msgid "exchange_rate"
+msgstr "exchange_rate"
+
+#. module: account_banking
+#: field:banking.import.line,duplicate:0
+msgid "Duplicate"
+msgstr "Duplicaat"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:1683
+#, python-format
+msgid "You have to define an analytic journal on the '%s' journal!"
+msgstr "Er moet een analytisch journaal worden ingesteld op het '%s' journaal!"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:1812
+#, python-format
+msgid "Configuration Error !"
+msgstr "Configuratiefout !"
+
+#. module: account_banking
+#: selection:account.bank.statement.line,state:0
+#: selection:payment.line,export_state:0
+msgid "Draft"
+msgstr "Draft"
+
+#. module: account_banking
+#: view:banking.transaction.wizard:0
+msgid "Disable reconciliation"
+msgstr "Afletteren ongedaan maken"
+
+#. module: account_banking
+#: model:ir.actions.act_window,name:account_banking.act_account_payment_account_bank_statement
+msgid "Bank Statements File"
+msgstr "Bankafschriftbestand"
+
+#. module: account_banking
+#: code:addons/account_banking/parsers/models.py:368
+#, python-format
+msgid "This is a stub. Please implement your own."
+msgstr "Dit is een stub. Maak alstublieft uw eigen versie."
+
+#. module: account_banking
+#: field:banking.import.transaction,type:0
+msgid "type"
+msgstr "type"
+
+#. module: account_banking
+#: view:account.banking.bank.import:0
+msgid "Import"
+msgstr "Import"
+
+#. module: account_banking
+#: field:banking.import.transaction,message:0
+msgid "message"
+msgstr "message"
+
+#. module: account_banking
+#: view:account.bank.statement:0
+#: view:account.bank.statement.line:0
+msgid "Confirm transaction"
+msgstr "Bevestig mutatie"
+
+#. module: account_banking
+#: field:banking.import.transaction,provision_costs_currency:0
+msgid "provision_costs_currency"
+msgstr "provision_costs_currency"
+
+#. module: account_banking
+#: constraint:account.bank.statement.line:0
+msgid "The amount of the voucher must be the same amount as the one on the statement line"
+msgstr "Het bedrag van de bon moet hetzelfde zijn als die op de bankafschriftregel"
+
+#. module: account_banking
+#: view:banking.transaction.wizard:0
+msgid "This bank transfer was marked as a duplicate. You can either confirm that this is not the case, or remove the bank transfer from the system."
+msgstr "Deze bankatransactie is gemarkeerd als een duplicaat. U kunt de bankafschriftregel verwijderen uit het systeem als dit het geval is, anders kunt u het kenmerk 'duplicaat' hier verwijderen."
+
+#. 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 bankafschriftbestanden"
+
+#. module: account_banking
+#: field:banking.import.line,invoice_ids:0
+msgid "unknown"
+msgstr "onbekend"
+
+#. module: account_banking
+#: field:banking.import.transaction,remote_bank_duns:0
+msgid "remote_bank_duns"
+msgstr "remote_bank_duns"
+
+#. module: account_banking
+#: field:banking.import.transaction,duplicate:0
+msgid "duplicate"
+msgstr "duplicaat"
+
+#. module: account_banking
+#: field:account.bank.statement,banking_id:0
+msgid "Imported File"
+msgstr "Geïmpoteerd bestand"
+
+#. module: account_banking
+#: selection:banking.import.line,transaction_type:0
+msgid "Aggregate payment order"
+msgstr "Verzamelde betaalopdracht"
+
+#. module: account_banking
+#: field:banking.import.transaction,remote_owner_custno:0
+msgid "remote_owner_custno"
+msgstr "remote_owner_custno"
+
+#. module: account_banking
+#: view:account.banking.imported.file:0
+#: field:account.banking.imported.file,log:0
+msgid "Import Log"
+msgstr "Impotlog"
+
+#. module: account_banking
+#: field:banking.import.line,date:0
+msgid "Date"
+msgstr "Datum"
+
+#. module: account_banking
+#: view:account.banking.account.settings:0
+msgid "Generation of Bank Costs Invoices"
+msgstr "Gegenereerde bankkostenfacturen"
+
+#. module: account_banking
+#: constraint:account.move.line:0
+msgid "Company must be same for its related account and period."
+msgstr "Bedrijf moet hetzelfde zijn voor de betreffende rekening en periode."
+
+#. module: account_banking
+#: field:banking.import.transaction,provision_costs:0
+msgid "provision_costs"
+msgstr "provision_costs"
+
+#. module: account_banking
+#: view:banking.transaction.wizard:0
+msgid "You can disable the reconciliation of this bank transfer"
+msgstr "U kunt het afletteren van de mutatie ongedaan maken"
+
+#. module: account_banking
+#: field:account.bank.statement.line,import_transaction_id:0
+#: field:banking.transaction.wizard,import_transaction_id:0
+msgid "Import transaction"
+msgstr "Geïmporteerde mutatie"
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/banktools.py:60
+#, python-format
+msgid "No suitable fiscal year found for date %(date)s and company %(company_name)s"
+msgstr "Geen geschikt boekjaar gevonden voor datum %(date)s en bedrijf %(company_name)s"
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/banking_transaction_wizard.py:153
+#, python-format
+msgid "No entry found for the selected invoice. \" +\n"
+" \"Try manual reconciliation."
+msgstr "Geen boeking gevonden voor de geselecteerde factuur. \" +\n"
+" \"Probeer handmatig af te letteren."
+
+#. module: account_banking
+#: field:banking.import.transaction,error_message:0
+msgid "error_message"
+msgstr "error_message"
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/banktools.py:82
+#, python-format
+msgid "Multiple overlapping periods for date %(date)s and company %(company_name)s"
+msgstr "Meerdere overlappende periodes gevonden voor datum %(date)s en bedrijf %(company_name)s"
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/bank_import.py:142
+#, 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:304
+#, python-format
+msgid "Number of statements loaded"
+msgstr "Aantal geladen afschriften"
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/bank_import.py:302
+#, 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/banking_import_transaction.py:1822
+#, python-format
+msgid "Statement %s is confirmed, journal items are created."
+msgstr "Afschrift %s is bevestigd, journaalposten worden aangemaakt."
+
+#. module: account_banking
+#: field:banking.import.transaction,statement:0
+msgid "statement"
+msgstr "statement"
+
+#. 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 "Bankieren"
+
+#. module: account_banking
+#: help:payment.mode,type:0
+msgid "Select the Payment Type for the Payment Mode."
+msgstr "Selecteer het type van de betaalmodus."
+
+#. module: account_banking
+#: view:banking.transaction.wizard:0
+msgid "You can let the system try to match this bank statement line again after you have made any changes in the database (for instance, add an invoice or a bank account)."
+msgstr "Laat de mutatie opnieuw matchen met de boekingen in het systeem nadat u de nodige wijzigingen in het systeem hebt aangebracht (bijvoorbeeld na het aanmaken van een factuur of het koppelen van een relatie aan een bepaalde bankrekening."
+
+#. module: account_banking
+#: help:payment.mode.type,code:0
+msgid "Specify the Code for Payment Type"
+msgstr "Geef de code op voor het betaaltype"
+
+#. module: account_banking
+#: selection:account.banking.imported.file,state:0
+msgid "Error"
+msgstr "Fout"
+
+#. module: account_banking
+#: field:payment.mode.type,ir_model_id:0
+msgid "Payment wizard"
+msgstr "Betaalwizard"
+
+#. module: account_banking
+#: view:banking.transaction.wizard:0
+msgid "Set write-off account"
+msgstr "Stel de grootboekrekening in ter afschrijving"
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/banking_transaction_wizard.py:152
+#, python-format
+msgid "No entry found for the selected invoice"
+msgstr "Geen boeking gevonden voor de geselecteerde factuur"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:358
+#, python-format
+msgid "Unable to link transaction id %(trans)s (ref: %(ref)s) to invoice: invoice %(invoice)s was already paid"
+msgstr "Kan de mutatie met id %(trans)s (referentie: %(ref)s) niet aan factuur %(invoice)s koppelen, deze is al betaald"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:1090
+#, python-format
+msgid "Cannot perform match on a confirmed transction"
+msgstr "Kan geen match uitvoeren op een bevestigde mutatie"
+
+#. module: account_banking
+#: field:banking.import.transaction,invoice_id:0
+#: field:banking.transaction.wizard,invoice_id:0
+msgid "Invoice to reconcile"
+msgstr "Factuur ter aflettering"
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/bank_import.py:294
+#, python-format
+msgid "Total number of statements"
+msgstr "Totaal aantal afschriften"
+
+#. module: account_banking
+#: field:banking.import.transaction,provision_costs_description:0
+msgid "provision_costs_description"
+msgstr "provision_costs_description"
+
+#. module: account_banking
+#: field:payment.mode.type,code:0
+msgid "Code"
+msgstr "Code"
+
+#. module: account_banking
+#: view:banking.transaction.wizard:0
+msgid "Manual match"
+msgstr "Handmatige match"
+
+#. module: account_banking
+#: code:addons/account_banking/account_banking.py:1018
+#, python-format
+msgid "Cannot unreconcile debit order: \"+\n"
+" \"Not implemented."
+msgstr "Kan aflettering van een incasso-opdracht niet ongedaa maken: \"+\n"
+" \"Niet geïmplementeerd."
+
+#. module: account_banking
+#: view:banking.transaction.wizard:0
+msgid "Multiple matches were found for this bank transfer. You must pick one of the matches or select a match manually below."
+msgstr "Meerdere overeenkomende boekingen zijn gevonden voor deze mutatie. U moet één van deze boekingen uitkiezen, of de mutatie handmatig matchen verderop in dit formulier."
+
+#. module: account_banking
+#: field:banking.transaction.wizard,payment_order_ids:0
+msgid "Matching payment orders"
+msgstr "Gekoppelde betaalopdrachten"
+
+#. module: account_banking
+#: view:account.banking.imported.file:0
+msgid "Import Details"
+msgstr "Importdetails"
+
+#. module: account_banking
+#: field:banking.import.transaction,local_account:0
+msgid "local_account"
+msgstr "local_account"
+
+#. module: account_banking
+#: field:account.bank.statement.line,international:0
+msgid "International Transaction"
+msgstr "Internationale mutatie"
+
+#. module: account_banking
+#: model:ir.model,name:account_banking.model_account_bank_statement
+msgid "Bank Statement"
+msgstr "Bankafschrift"
+
+#. module: account_banking
+#: field:banking.import.transaction,remote_owner_postalcode:0
+msgid "remote_owner_postalcode"
+msgstr "remote_owner_postalcode"
+
+#. module: account_banking
+#: view:banking.transaction.wizard:0
+#: selection:payment.line,export_state:0
+msgid "Done"
+msgstr "Gereed"
+
+#. module: account_banking
+#: selection:account.bank.statement.line,match_type:0
+#: selection:banking.import.transaction,match_type:0
+#: model:ir.model,name:account_banking.model_account_invoice
+msgid "Invoice"
+msgstr "Factuur"
+
+#. module: account_banking
+#: code:addons/account_banking/parsers/models.py:272
+#, python-format
+msgid "Invalid value for transfer_type"
+msgstr "Ongeldige waarde voor transfer_type"
+
+#. module: account_banking
+#: view:payment.order:0
+msgid "Select Invoices to Pay"
+msgstr "Kies te betalen facturen"
+
+#. module: account_banking
+#: view:account.banking.bank.import:0
+msgid "Cancel"
+msgstr "Annuleer"
+
+#. module: account_banking
+#: view:account.banking.bank.import:0
+msgid "Close"
+msgstr "Sluiten"
+
+#. module: account_banking
+#: field:banking.import.transaction,remote_owner_address:0
+msgid "remote_owner_address"
+msgstr "remote_owner_address"
+
+#. module: account_banking
+#: model:ir.model,name:account_banking.model_account_move_line
+msgid "Journal Items"
+msgstr "Boekingen"
+
+#. module: account_banking
+#: field:account.banking.imported.file,user_id:0
+msgid "Responsible User"
+msgstr "Verantwoordelijk gebruiker"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:1680
+#, python-format
+msgid "No Analytic Journal !"
+msgstr "Geen analytisch dagboek !"
+
+#. module: account_banking
+#: selection:account.banking.imported.file,state:0
+msgid "Unfinished"
+msgstr "Niet gereed"
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/banking_transaction_wizard.py:176
+#, python-format
+msgid "Cannot select for reconcilion"
+msgstr "Kan niet selecteren voor afletteren"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:1089
+#, python-format
+msgid "Cannot perform match"
+msgstr "Kan de match niet uitvoeren"
+
+#. module: account_banking
+#: model:ir.model,name:account_banking.model_payment_order
+msgid "Payment Order"
+msgstr "Betalingsopdracht"
+
+#. 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/banking_import_transaction.py:781
+#, python-format
+msgid "Bank transaction %s: write off not implemented for \" +\n"
+" \"this match type."
+msgstr "Bankmutatie %s: afschrijven niet geïmplementeerd voor \" +\n"
+" \"dit matchtype."
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:645
+#, python-format
+msgid "Cannot unreconcile: no direct debit order"
+msgstr "Kan niet afletteren: geen incasso-opdracht"
+
+#. module: account_banking
+#: code:addons/account_banking/account_banking.py:1352
+#: code:addons/account_banking/account_banking.py:1356
+#: constraint:res.partner.bank:0
+#, python-format
+msgid "The IBAN number doesn't seem to be correct"
+msgstr "Het IBAN-nummer lijkt niet correct te zijn"
+
+#. module: account_banking
+#: selection:banking.import.line,transaction_type:0
+msgid "Bank costs"
+msgstr "Bankkosten"
+
+#. module: account_banking
+#: field:account.bank.statement.line,match_multi:0
+#: field:banking.import.transaction,match_multi:0
+msgid "Multi match"
+msgstr "Multimatch"
+
+#. module: account_banking
+#: field:banking.import.transaction,remote_owner_city:0
+msgid "remote_owner_city"
+msgstr "remote_owner_city"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:1142
+#, python-format
+msgid "Transaction found for unknown account %(bank_account)s"
+msgstr "Mutatie gevonden voor onbekende bankrekening %(bank_account)s"
+
+#. module: account_banking
+#: selection:account.banking.bank.import,state:0
+msgid "ready"
+msgstr "ready"
+
+#. module: account_banking
+#: field:banking.import.transaction,move_line_ids:0
+msgid "Matching entries"
+msgstr "Overeenkomende boekingsregels"
+
+#. module: account_banking
+#: field:account.banking.bank.import,parser:0
+#: field:account.banking.imported.file,format:0
+msgid "File Format"
+msgstr "Bestandsformaat"
+
+#. module: account_banking
+#: code:addons/account_banking/account_banking.py:922
+#, python-format
+msgid "You can only combine payment orders of the same type"
+msgstr "Alleen betaalopdrachten van hetzelfde type kunnen worden gecombineerd"
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/banktools.py:66
+#, python-format
+msgid "Multiple overlapping fiscal years found for date %(date)s and company %(company_name)s"
+msgstr "Meerdere overlappende boekjaren gevonden voor datum %(date)s en bedrijf %(company_name)s"
+
+#. module: account_banking
+#: field:account.banking.account.settings,journal_id:0
+msgid "Journal"
+msgstr "Dagboek"
+
+#. module: account_banking
+#: field:account.banking.account.settings,costs_account_id:0
+msgid "Bank Costs Account"
+msgstr "Grootboekrekening bankkosten"
+
+#. module: account_banking
+#: selection:account.banking.imported.file,state:0
+msgid "Finished"
+msgstr "Gereed"
+
+#. module: account_banking
+#: selection:payment.line,export_state:0
+msgid "Sent"
+msgstr "Verstuurd"
+
+#. module: account_banking
+#: model:ir.model,name:account_banking.model_payment_line
+msgid "Payment Line"
+msgstr "Betaalregel"
+
+#. module: account_banking
+#: view:account.banking.account.settings:0
+msgid "Bank Account Details"
+msgstr "Details bankrekening"
+
+#. module: account_banking
+#: field:banking.import.transaction,remote_owner_country_code:0
+msgid "remote_owner_country_code"
+msgstr "remote_owner_country_code"
+
+#. module: account_banking
+#: selection:account.bank.statement.line,match_type:0
+#: selection:banking.import.transaction,match_type:0
+#: selection:payment.mode.type,payment_order_type:0
+#: selection:payment.order,payment_order_type:0
+msgid "Payment"
+msgstr "Betaling"
+
+#. module: account_banking
+#: model:ir.model,name:account_banking.model_res_bank
+msgid "Bank"
+msgstr "Bank"
+
+#. module: account_banking
+#: selection:banking.import.line,type:0
+msgid "Customer"
+msgstr "Klant"
+
+#. module: account_banking
+#: code:addons/account_banking/account_banking.py:1008
+#: code:addons/account_banking/banking_import_transaction.py:468
+#: code:addons/account_banking/banking_import_transaction.py:600
+#: code:addons/account_banking/banking_import_transaction.py:604
+#: code:addons/account_banking/banking_import_transaction.py:772
+#: code:addons/account_banking/banking_import_transaction.py:780
+#, python-format
+msgid "Cannot reconcile"
+msgstr "Kan niet afletteren"
+
+#. module: account_banking
+#: field:banking.import.line,name:0
+#: field:banking.transaction.wizard,name:0
+#: field:payment.mode.type,name:0
+msgid "Name"
+msgstr "Naam"
+
+#. module: account_banking
+#: help:account.banking.account.settings,costs_account_id:0
+msgid "The account to use when the bank invoices its own costs. Leave it blank to disable automatic invoice generation on bank costs."
+msgstr "De grootboekrekening waarop facturen met bankkosten kunnen worden geboekt. Laat leeg om het genereren van dergelijke facturen uit te schakelen."
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/bank_import.py:206
+#, python-format
+msgid "Statements found for account %(bank_account)s, but no default journal was defined."
+msgstr "Bankafschriften gevonden voor bankrekening %(bank_account)s, maar er is geen standaard dagboek ingesteld."
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/bank_import.py:329
+#, python-format
+msgid "Review Bank Statements"
+msgstr "Inzage bankafschriften"
+
+#. module: account_banking
+#: field:account.bank.statement.line,partner_bank_id:0
+#: field:account.banking.account.settings,partner_bank_id:0
+#: field:banking.import.line,partner_bank_id:0
+msgid "Bank Account"
+msgstr "Bankrekening"
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/bank_import.py:306
+#, python-format
+msgid "Number of transactions loaded"
+msgstr "Aantal geladen transacties"
+
+#. module: account_banking
+#: field:banking.import.transaction,payment_order_ids:0
+msgid "Payment orders"
+msgstr "Betaalopdrachten"
+
+#. module: account_banking
+#: view:banking.transaction.wizard:0
+msgid "System match"
+msgstr "Automatische match"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:581
+#: code:addons/account_banking/banking_import_transaction.py:685
+#, python-format
+msgid "No direct debit order item"
+msgstr "Geen onderdeel van een incasso-opdracht"
+
+#. module: account_banking
+#: code:addons/account_banking/banking_import_transaction.py:689
+#, python-format
+msgid "The direct debit order item is not marked for storno"
+msgstr "Dit item uit de incasso-opdracht is niet gemarkeerd als storno"
+
+#. module: account_banking
+#: model:ir.model,name:account_banking.model_payment_order_create
+msgid "payment.order.create"
+msgstr "payment.order.create"
+
+#. module: account_banking
+#: code:addons/account_banking/account_banking.py:1009
+#, python-format
+msgid "Cannot reconcile debit order: \"+\n"
+" \"Not implemented."
+msgstr "Kan een incasso-opdracht niet afletteren: \"+\n"
+" \"Niet geïmplementeerd."
+
+#. module: account_banking
+#: field:banking.import.transaction,local_currency:0
+msgid "local_currency"
+msgstr "local_currency"
+
+#. module: account_banking
+#: field:banking.import.transaction,reference:0
+msgid "reference"
+msgstr "reference"
+
+#. module: account_banking
+#: model:res.partner.bank.type.field,name:account_banking.bank_acc_number_field
+msgid "acc_number"
+msgstr "rekeningnummer"
+
+#. module: account_banking
+#: field:payment.mode,type:0
+msgid "Payment type"
+msgstr "Betaaltype"
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/bank_import.py:308
+#, python-format
+msgid "Number of transactions matched"
+msgstr "Aantal transacties herkend"
+
+#. module: account_banking
+#: field:banking.import.transaction,transaction:0
+msgid "transaction"
+msgstr "mutatie"
+
+#. module: account_banking
+#: code:addons/account_banking/wizard/banktools.py:200
+#, 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
+#: field:banking.import.transaction,remote_account:0
+msgid "remote_account"
+msgstr "remote_account"
+
+#. module: account_banking
+#: model:ir.model,name:account_banking.model_banking_import_line
+msgid "Bank import lines"
+msgstr "Bankimportregels"
+
+#. module: account_banking
+#: help:account.banking.account.settings,bank_partner_id:0
+msgid "The partner to use for bank costs. Banks are not partners by default. You will most likely have to create one."
+msgstr "De relatie te gebruiken op facturen voor bankkosten. Waarschijnlijk moet u een nieuwe relatie aanmaken."
+
+#. 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 grootboekrekening voor het boeken van een overwachte betaling. De rekening wordt gebruikt wanneer er geen bijbehorende boeking kan worden gevonden in het systeem. Merk op dat de boekingen nog kunnen worden aangepast vóór het bevestigen ervan."
+
+#. module: account_banking
+#: model:ir.model,name:account_banking.model_account_banking_imported_file
+msgid "Imported Bank Statements File"
+msgstr "Geïmporteerd bankafschriftbestand"
+
+#. module: account_banking
+#: field:banking.import.transaction,remote_owner:0
+msgid "remote_owner"
+msgstr "remote_owner"
+
+#. module: account_banking
+#: selection:account.bank.statement.line,match_type:0
+#: selection:banking.import.transaction,match_type:0
+msgid "Storno"
+msgstr "Storno"
+
+#. module: account_banking
+#: constraint:account.move.line:0
+msgid "You can not create move line on view account."
+msgstr "U kunt geen boekingsregel creëren op een zichtrekening"
=== removed file 'account_banking/i18n/nl_NL.po'
--- account_banking/i18n/nl_NL.po 2011-02-15 09:05:56 +0000
+++ account_banking/i18n/nl_NL.po 1970-01-01 00:00:00 +0000
@@ -1,905 +0,0 @@
-# Translation of OpenERP Server.
-# This file contains the translation of the following modules:
-# * account_banking
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: OpenERP Server 5.0.12\n"
-"Report-Msgid-Bugs-To: support@xxxxxxxxxxx\n"
-"POT-Creation-Date: 2011-02-12 13:17:22+0000\n"
-"PO-Revision-Date: 2010-07-16 14:58:26+0000\n"
-"Last-Translator: Piueter J. Kersten <p.j.kersten@xxxxxxxxxxx>\n"
-"Language-Team: Dutch\n"
-"Language: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: \n"
-
-#. module: account_banking
-#: code:addons/account_banking/wizard/bank_import.py:0
-#, python-format
-msgid "Number of bank costs invoices created"
-msgstr "Aantal bankkosten-facturen aangemaakt"
-
-#. module: account_banking
-#: wizard_view:account_banking.banking_import,view_error:0
-#: wizard_view:account_banking.banking_import,view_statements: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
-#: code:addons/account_banking/wizard/bank_import.py:0
-#, python-format
-msgid ""
-"Unable to link transaction id %(trans)s (ref: %(ref)s) to invoice: '\n"
-" '%(no_candidates)s candidates found; can\'t choose."
-msgstr ""
-"Niet in staat transactie id %(trans)s (ref: %(ref)s) aan factuur te "
-"koppelen: '\n"
-" '%(no_candidates)s kandidaten gevonden; kan niet "
-"kiezen."
-
-#. module: account_banking
-#: model:ir.model,name:account_banking.model_account_banking_settings
-msgid "Settings for the account_banking module"
-msgstr "Instellingen voor de account_banking module"
-
-#. 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
-#: field:payment.line,date_done:0
-msgid "Date Confirmed"
-msgstr "Bevestigingsdatum"
-
-#. module: account_banking
-#: wizard_button:account_banking.banking_import,view_statements,open_statements:0
-msgid "_View Statements"
-msgstr "_Bekijk bankafschriften"
-
-#. module: account_banking
-#: wizard_button:account_banking.banking_import,view_error,end:0
-#: wizard_button:account_banking.banking_import,view_statements,end:0
-msgid "_Close"
-msgstr "_Sluit"
-
-#. module: account_banking
-#: field:account.banking.account.settings,bank_partner_id:0
-msgid "Bank Partner"
-msgstr "Bank-relatie"
-
-#. 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_no)s niet gevonden in de database"
-
-#. module: account_banking
-#: view:account.banking.account.settings:0
-msgid "Generation of Bank Costs Invoices"
-msgstr "Aanmaken van bankkosten-facturen"
-
-#. 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 "Free Reference"
-msgstr "Vrije referentie"
-
-#. 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/account_banking.py:0
-#, python-format
-msgid "Structured Reference"
-msgstr "Acceptgiro"
-
-#. module: account_banking
-#: field:account.banking.account.settings,invoice_journal_id:0
-msgid "Costs Journal"
-msgstr "Dagboek bankkosten"
-
-#. 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
-#: code:addons/account_banking/account_banking.py:0
-#, python-format
-msgid "Invalid IBAN account number!"
-msgstr "Ongeldig IBAN-rekeningnummer"
-
-#. 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
-#: field:account.banking.imported.file,file:0
-msgid "Raw Data"
-msgstr "Ruwe data"
-
-#. module: account_banking
-#: selection:payment.line,export_state:0
-msgid "Cancelled"
-msgstr "Geannuleerd"
-
-#. module: account_banking
-#: code:addons/account_banking/account_banking.py:0
-#, python-format
-msgid ""
-"Insufficient data to select online '\n"
-" 'conversion database"
-msgstr ""
-"Onvoldoende gegevens om on-line'\n"
-" 'conversie-database te kiezen"
-
-#. 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
-#: code:addons/account_banking/wizard/banktools.py:0
-#, python-format
-msgid "Unknown Bank"
-msgstr "Onbekende bank"
-
-#. module: account_banking
-#: wizard_button:account_banking.banking_import,init,end:0
-msgid "_Cancel"
-msgstr "_Annuleer"
-
-#. module: account_banking
-#: code:addons/account_banking/account_banking.py:0
-#, python-format
-msgid "Invalid format"
-msgstr "Ongeldig formaat"
-
-#. 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
-#: model:ir.actions.act_window,name:account_banking.action_account_banking_res_partner_banks
-#: model:ir.ui.menu,name:account_banking.menu_action_account_banking_bank_accounts
-msgid "Bank Accounts"
-msgstr "Bankrekeningen"
-
-#. 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
-#: 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.banking.account.settings,default_credit_account_id:0
-msgid "Default credit account"
-msgstr "Standaard credit-rekening"
-
-#. 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
-#: help:account.banking.account.settings,invoice_journal_id:0
-msgid "This is the journal used to create invoices for bank costs."
-msgstr ""
-"Dit is het dagboek dat gebruikt wordt bij het aanmaken van bankkosten-"
-"facturen."
-
-#. 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
-#: help:account.banking.account.settings,costs_account_id:0
-msgid ""
-"The account to use when the bank invoices its own costs. Leave it blank to "
-"disable automatic invoice generation on bank costs."
-msgstr ""
-"De te gebruiken grootboekrekening wanneer de bank zijn kosten incasseert. "
-"Laat leeg om het automatisch aanmaken van facturen voor bankkosten uit te "
-"zetten."
-
-#. 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 "Invalid data"
-msgstr "Ongeldige gegevens"
-
-#. module: account_banking
-#: model:res.partner.bank.type.field,name:account_banking.bank_normal_field_contry
-msgid "country_id"
-msgstr "Landcode"
-
-#. module: account_banking
-#: selection:payment.line,export_state:0
-msgid "Rejected"
-msgstr "Geweigerd"
-
-#. 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
-#: model:payment.type,name:account_banking.manual_bank_tranfer
-msgid "Manual Bank Transfer"
-msgstr ""
-
-#. module: account_banking
-#: code:addons/account_banking/sepa/bbantoiban.py:0
-#, python-format
-msgid "This is a stub. Please implement your own code"
-msgstr "Dit is een stub. Maak alstublieft uw eigen versie."
-
-#. 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
-#: model:ir.module.module,description:account_banking.module_meta_information
-msgid ""
-"\n"
-" Module to do banking.\n"
-"\n"
-" Note: This module is depending on BeautifulSoup.\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"
-" + The SWIFT database is consulted for bank information.\n"
-"\n"
-" * Adds dropin extensible import facility for bank communication in:\n"
-" - Drop-in input parser development.\n"
-" - MultiBank (NL) format transaction files available as\n"
-" account_banking_nl_multibank,\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"
-"\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"
-" Waarschuwing: deze module is afhankelijk van BeautifulSoup.\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"
-" + Elke bank kan in zijn eigen tempo SEPA invoeren in diens \n"
-" communicatie met de klant.\n"
-" + Nationale online databases kunnen gebruikt worden om BBAN's naar\n"
-" IBAN's te converteren.\n"
-"\n"
-" * Geeft dropin uitbreidbare importvoorzieningen voor bankcommunicatie "
-"in:\n"
-" + MultiBank (NL) formaat transactiebestanden,\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"
-"\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
-#: wizard_view:account_banking.banking_import,init:0
-#: wizard_view:account_banking.banking_import,view_error:0
-#: wizard_view:account_banking.banking_import,view_statements: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/bank_import.py:0
-#, python-format
-msgid "Candidates: %(candidates)s"
-msgstr "Kandidaten: %(candidate)s"
-
-#. 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/bank_import.py:0
-#, python-format
-msgid "ERROR!"
-msgstr "FOUT!"
-
-#. module: account_banking
-#: code:addons/account_banking/wizard/bank_import.py:0
-#, python-format
-msgid ""
-"Unable to link transaction id %(trans)s '\n"
-" '(ref: %(ref)s) to invoice: '\n"
-" 'invoice %(invoice)s was already paid"
-msgstr ""
-"Niet in staat transactie id %(trans)s '\n"
-" '(ref: %(ref)s) met factuur te koppelen: '\n"
-" 'factuur %(invoice)s was al betaald"
-
-#. module: account_banking
-#: code:addons/account_banking/account_banking.py:0
-#, python-format
-msgid "The account number has the wrong format for %(country)s"
-msgstr "Het rekeningnummer heeft het verkeerde formaat voor %(country)s"
-
-#. module: account_banking
-#: field:account.bank.statement.line,currency:0
-msgid "Currency"
-msgstr "Valuta"
-
-#. module: account_banking
-#: field:payment.line,msg:0
-msgid "Message"
-msgstr "Bericht"
-
-#. 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,view_error,log:0
-#: wizard_field:account_banking.banking_import,view_statements,log:0
-msgid "Log"
-msgstr "Logboek"
-
-#. module: account_banking
-#: code:addons/account_banking/parsers/models.py:0
-#, python-format
-msgid "Invalid value for transfer_type"
-msgstr "Ongeldige waarde voor transfer_type"
-
-#. module: account_banking
-#: code:addons/account_banking/account_banking.py:0
-#, python-format
-msgid "Insufficient data"
-msgstr "Onvoldoende gegevens"
-
-#. module: account_banking
-#: code:addons/account_banking/account_banking.py:0
-#, python-format
-msgid "Configration Error !"
-msgstr "Instellingsfout!"
-
-#. 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
-#: code:addons/account_banking/wizard/banktools.py:0
-#, python-format
-msgid "No suitable period found for date %(date)s and company %(company_name)s"
-msgstr ""
-"Geen geschikt boekjaar gevonden voor datum %(date)s en bedrijf "
-"%(company_name)s"
-
-#. 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
-#: model:ir.actions.wizard,name:account_banking.wizard_account_banking_payment_manual
-msgid "Manual Bank Payment"
-msgstr "Handmatige betaling via bank"
-
-#. 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/banktools.py:0
-#, python-format
-msgid ""
-"Multiple overlapping periods for date %(date)s and company %(company_name)s"
-msgstr ""
-"Meerdere overlappende periodes gevonden voor datum %(date)s en bedrijf "
-"%(company_name)s"
-
-#. 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/wizard/bank_import.py:0
-#, python-format
-msgid "Total number of statements"
-msgstr "Totaal aantal afschriften"
-
-#. 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
-#: code:addons/account_banking/wizard/banktools.py:0
-#, python-format
-msgid ""
-"No suitable fiscal year found for date %(date)s and company %(company_name)s"
-msgstr ""
-"Geen geschikt boekjaar gevonden voor datum %(date)s en bedrijf "
-"%(company_name)s"
-
-#. 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 van het afschrift klopt niet !"
-
-#. 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
-#: wizard_button:account_banking.banking_import,view_error,open_import:0
-msgid "_View Imported File"
-msgstr "_Bekijk geïmporteerd bestand"
-
-#. module: account_banking
-#: code:addons/account_banking/account_banking.py:0
-#, python-format
-msgid "The IBAN number doesn't seem to be correct"
-msgstr "Het IBAN-nummer lijkt niet correct te zijn"
-
-#. 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
-#: code:addons/account_banking/wizard/banktools.py:0
-#, python-format
-msgid ""
-"Multiple overlapping fiscal years found for date %(date)s and company "
-"%(company_name)s"
-msgstr ""
-"Meerdere overlappende boekjaren gevonden voor datum %(date)s en bedrijf "
-"%(company_name)s"
-
-#. module: account_banking
-#: field:account.banking.account.settings,journal_id:0
-msgid "Journal"
-msgstr "Dagboek"
-
-#. module: account_banking
-#: field:account.banking.account.settings,costs_account_id:0
-msgid "Bank Costs Account"
-msgstr "Bankkosten-rekening"
-
-#. module: account_banking
-#: selection:account.banking.imported.file,state:0
-msgid "Finished"
-msgstr "Gereed"
-
-#. module: account_banking
-#: selection:payment.line,export_state:0
-msgid "Draft"
-msgstr "Concept"
-
-#. module: account_banking
-#: view:account.banking.account.settings:0
-msgid "Bank Account Details"
-msgstr "Details bankrekening"
-
-#. 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/account_banking.py:0
-#, python-format
-msgid "The account number appears to be invalid for %(country)s"
-msgstr "Het bankrekeningnummer blijkt ongeldig te zijn voor %(country)s"
-
-#. module: account_banking
-#: model:res.partner.bank.type.field,name:account_banking.bank_acc_number_field
-msgid "acc_number"
-msgstr "rekeningnummer"
-
-#. module: account_banking
-#: code:addons/account_banking/wizard/bank_import.py:0
-#, python-format
-msgid "Number of transactions matched"
-msgstr "Aantal transacties herkend"
-
-#. module: account_banking
-#: help:account.banking.account.settings,bank_partner_id:0
-msgid ""
-"The partner to use for bank costs. Banks are not partners by default. You "
-"will most likely have to create one."
-msgstr ""
-"De relatie om te gebruiken voor bankkosten. Banken zijn standaard geen "
-"relatie. U zult hoogstwaarschijnlijk één moeten aanmaken."
-
-#. 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 in een geldige toestand."
-
-#. module: account_banking
-#: model:ir.model,name:account_banking.model_account_banking_imported_file
-msgid "Imported Bank Statements File"
-msgstr "Geïmporteerd bankafschrift bestand"
-
-#. module: account_banking
-#: code:addons/account_banking/wizard/bank_import.py:0
-#, python-format
-msgid ""
-"Statement %(statement_id)s for account %(bank_account)s' \n"
-" ' uses different currency than the defined bank "
-"journal."
-msgstr ""
-"Bankafschrift %(statement_id)s voor bankrekening %(bank_account)s' \n"
-" ' gebruikt een andere valuta dan in het opgegeven "
-"bankdagboek."
=== added directory 'account_banking/migrations'
=== added directory 'account_banking/migrations/0.1.81'
=== added file 'account_banking/migrations/0.1.81/post-set-statement-line-state.py'
--- account_banking/migrations/0.1.81/post-set-statement-line-state.py 1970-01-01 00:00:00 +0000
+++ account_banking/migrations/0.1.81/post-set-statement-line-state.py 2012-01-17 10:38:36 +0000
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (C) 2011 Therp BV (<http://therp.nl>)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU 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/>.
+#
+##############################################################################
+
+""" r81: introduction of bank statement line state
+"""
+__name__ = ("account.bank.statement.line:: set new field 'state' to "
+ "confirmed for all statement lines belonging to confirmed "
+ "statements")
+
+def migrate(cr, version):
+ cr.execute ("UPDATE account_bank_statement_line as sl "
+ " SET state = 'confirmed'"
+ " FROM account_bank_statement as s "
+ " WHERE sl.statement_id = s.id "
+ " AND s.state = 'confirm' "
+ )
=== modified file 'account_banking/parsers/models.py'
--- account_banking/parsers/models.py 2011-04-28 08:07:51 +0000
+++ account_banking/parsers/models.py 2012-01-17 10:38:36 +0000
@@ -174,6 +174,10 @@
# An error message for interaction with the user
# Only used when mem_transaction.valid returns False.
'error_message',
+
+ # Storno attribute. When True, make the cancelled debit eligible for
+ # a next direct debit run
+ 'storno_retry',
]
@@ -206,6 +210,10 @@
# PERIODIC_ORDER An automated payment by the bank on your behalf.
# Always outgoing.
# Will be selected for matching.
+ # STORNO A failed or reversed attempt at direct debit.
+ # Either due to an action on the payer's side
+ # or a failure observed by the bank (lack of
+ # credit for instance)
#
# Perhaps more will follow.
#
@@ -223,10 +231,11 @@
PAYMENT_BATCH = 'PB'
PAYMENT_TERMINAL = 'PT'
PERIODIC_ORDER = 'PO'
+ STORNO = 'ST'
types = [
BANK_COSTS, BANK_TERMINAL, CHECK, DIRECT_DEBIT, ORDER,
- PAYMENT_BATCH, PAYMENT_TERMINAL, PERIODIC_ORDER,
+ PAYMENT_BATCH, PAYMENT_TERMINAL, PERIODIC_ORDER, STORNO,
]
type_map = {
# This can be a translation map of type versus bank type. Each key is
@@ -331,7 +340,20 @@
country_code = None
doc = __doc__
- def parse(self, data):
+ def get_unique_statement_id(self, cr, base):
+ name = base
+ suffix = 1
+ while True:
+ cr.execute(
+ "select id from account_bank_statement where name = %s",
+ (name,))
+ if not cr.rowcount:
+ break
+ suffix += 1
+ name = "%s-%d" % (base, suffix)
+ return name
+
+ def parse(self, cr, data):
'''
Parse data.
=== modified file 'account_banking/security/ir.model.access.csv'
--- account_banking/security/ir.model.access.csv 2011-07-21 11:30:59 +0000
+++ account_banking/security/ir.model.access.csv 2012-01-17 10:38:36 +0000
@@ -3,3 +3,5 @@
"access_account_banking_settings_user","account.banking.account.settings user","model_account_banking_account_settings","account.group_account_user",1,0,0,0
"access_account_banking_import","account.bankimport","model_account_banking_imported_file","account.group_account_user",1,1,1,1
"access_payment_mode_type","payment.mode.type","model_payment_mode_type","account_payment.group_account_payment",1,1,1,1
+"access_banking_import_transaction","Banking addons - Bank import transaction","model_banking_import_transaction","account.group_account_user",1,1,1,1
+"access_banking_transaction_wizard","Banking addons - Transaction wizard","model_banking_transaction_wizard","account.group_account_user",1,1,1,1
=== modified file 'account_banking/wizard/__init__.py'
--- account_banking/wizard/__init__.py 2011-07-21 11:30:59 +0000
+++ account_banking/wizard/__init__.py 2012-01-17 10:38:36 +0000
@@ -21,5 +21,6 @@
import bank_import
import bank_payment_manual
import account_payment_order
+import banking_transaction_wizard
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
=== modified file 'account_banking/wizard/account_payment_order.py'
--- account_banking/wizard/account_payment_order.py 2011-07-21 11:30:59 +0000
+++ account_banking/wizard/account_payment_order.py 2012-01-17 10:38:36 +0000
@@ -91,11 +91,17 @@
else:
state = 'normal'
communication2 = line.invoice.reference
+ # support debit orders when enabled
+ if (payment.payment_order_type == 'debit' and
+ 'amount_to_receive' in line):
+ amount_currency = line.amount_to_receive
+ else:
+ amount_currency = line.amount_to_pay
### end account_banking
payment_obj.create(cr, uid,{
'move_line_id': line.id,
- 'amount_currency': line.amount_to_pay,
+ 'amount_currency': amount_currency,
'bank_id': line2bank.get(line.id),
'order_id': payment.id,
'partner_id': line.partner_id and line.partner_id.id or False,
=== modified file 'account_banking/wizard/bank_import.py'
--- account_banking/wizard/bank_import.py 2011-11-01 14:57:39 +0000
+++ account_banking/wizard/bank_import.py 2012-01-17 10:38:36 +0000
@@ -54,435 +54,70 @@
'''
return models.parser_type.get_parser_types()
+class banking_import_line(osv.osv_memory):
+ _name = 'banking.import.line'
+ _description = 'Bank import lines'
+ _columns = {
+ 'name': fields.char('Name', size=64),
+ 'date': fields.date('Date', readonly=True),
+ 'amount': fields.float('Amount', digits_compute=dp.get_precision('Account')),
+ 'statement_line_id': fields.many2one(
+ 'account.bank.statement.line',
+ 'Resulting statement line', readonly=True),
+ 'type': fields.selection([
+ ('supplier','Supplier'),
+ ('customer','Customer'),
+ ('general','General')
+ ], 'Type', required=True),
+ 'partner_id': fields.many2one('res.partner', 'Partner'),
+ 'statement_id': fields.many2one('account.bank.statement', 'Statement',
+ select=True, required=True, ondelete='cascade'),
+ 'ref': fields.char('Reference', size=32),
+ 'note': fields.text('Notes'),
+ 'period_id': fields.many2one('account.period', 'Period'),
+ 'currency': fields.many2one('res.currency', 'Currency'),
+ 'banking_import_id': fields.many2one(
+ 'account.banking.bank.import', 'Bank import',
+ readonly=True, ondelete='cascade'),
+ 'reconcile_id': fields.many2one(
+ 'account.move.reconcile', 'Reconciliaton'),
+ 'account_id': fields.many2one('account.account', 'Account'),
+ 'invoice_ids': fields.many2many(
+ 'account.invoice', 'banking_import_line_invoice_rel',
+ 'line_id', 'invoice_id'),
+ 'payment_order_id': fields.many2one('payment.order', 'Payment order'),
+ 'partner_bank_id': fields.many2one('res.partner.bank', 'Bank Account'),
+ 'transaction_type': fields.selection([
+ # TODO: payment terminal etc...
+ ('invoice', 'Invoice payment'),
+ ('payment_order_line', 'Payment from a payment order'),
+ ('payment_order', 'Aggregate payment order'),
+ ('storno', 'Canceled debit order'),
+ ('bank_costs', 'Bank costs'),
+ ('unknown', 'Unknown'),
+ ], 'Transaction type'),
+ 'duplicate': fields.boolean('Duplicate'),
+ }
+banking_import_line()
+
+
class banking_import(osv.osv_memory):
_name = 'account.banking.bank.import'
- def _get_move_info(self, cursor, uid, move_line, partner_bank_id=False,
- partial=False):
- reconcile_obj = self.pool.get('account.move.reconcile')
- type_map = {
- 'out_invoice': 'customer',
- 'in_invoice': 'supplier',
- 'out_refund': 'customer',
- 'in_refund': 'supplier',
- }
- retval = struct(move_line=move_line, partner_id=move_line.partner_id.id,
- partner_bank_id=partner_bank_id,
- 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'
-
- if partial:
- move_line.reconcile_partial_id = reconcile_obj.create(
- cursor, uid, {
- 'type': 'auto',
- 'line_partial_ids': [(4, 0, [move_line.id])]
- }
- )
- else:
- if move_line.reconcile_partial_id:
- partial_ids = [x.id for x in
- move_line.reconcile_partial_id.line_partial_ids
- ]
- else:
- partial_ids = []
- move_line.reconcile_id = reconcile_obj.create(
- cursor, uid, {
- 'type': 'auto',
- 'line_id': [
- (4, x, False) for x in [move_line.id] + partial_ids
- ],
- 'line_partial_ids': [
- (3, x, False) for x in partial_ids
- ]
- }
- )
- return retval
-
- def _link_payment(self, cursor, uid, trans, payment_lines,
- partner_ids, bank_account_ids, log, linked_payments):
- '''
- Find the payment order belonging to this reference - if there is one
- This is the easiest part: when sending payments, the returned bank info
- 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 = dp.get_precision('Account')(cursor)[1]
- 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]
- # Check cache to prevent multiple matching of a single payment
- if candidate.id not in linked_payments:
- linked_payments[candidate.id] = True
- payment_line_obj = self.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 self._get_move_info(cursor, uid, candidate.move_line_id)
-
- return False
-
- def _link_invoice(self, cursor, uid, trans, move_lines,
- partner_ids, bank_account_ids, log, linked_invoices):
- '''
- 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.
- 2. Debit amounts are either customer invoices or credited supplier
- invoices.
- 3. Credit amounts are either supplier invoices or credited customer
- invoices.
- 4. Payments are either below expected amount or only slightly above
- (abs).
- 5. 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.
-
- Return values:
- move_info: the move_line information belonging to the matched
- invoice
- new_trans: the new transaction when the current one was split.
- This can happen when multiple invoices were paid with a single
- bank transaction.
- '''
- def eyecatcher(invoice):
- '''
- Return the eyecatcher for an invoice
- '''
- return invoice.type.startswith('in_') and invoice.name or \
- invoice.number
-
- def has_id_match(invoice, ref, msg):
- '''
- Aid for debugging - way more comprehensible than complex
- comprehension filters ;-)
-
- Match on ID of invoice (reference, name or number, whatever
- available and sensible)
- '''
- if invoice.reference:
- # Reference always comes first, as it is manually set for a
- # reason.
- iref = invoice.reference.upper()
- if iref in ref or iref in msg:
- return True
- if invoice.type.startswith('in_'):
- # Internal numbering, no likely match on number
- if invoice.name:
- iname = invoice.name.upper()
- if iname in ref or iname in msg:
- return True
- elif invoice.type.startswith('out_'):
- # External id's possible and likely
- inum = invoice.number.upper()
- if inum in ref or inum in msg:
- return True
-
- return False
-
- def _cached(move_line):
- '''Check if the move_line has been cached'''
- return move_line.id in linked_invoices
-
- def _cache(move_line, remaining=0.0):
- '''Cache the move_line'''
- linked_invoices[move_line.id] = remaining
-
- def _remaining(move_line):
- '''Return the remaining amount for a previously matched move_line
- '''
- return linked_invoices[move_line.id]
-
- def _sign(invoice):
- '''Return the direction of an invoice'''
- return {'in_invoice': -1,
- 'in_refund': 1,
- 'out_invoice': 1,
- 'out_refund': -1
- }[invoice.type]
-
- digits = dp.get_precision('Account')(cursor)[1]
- partial = False
-
- # Search invoice on partner
- if partner_ids:
- candidates = [x for x in move_lines
- if x.partner_id.id in partner_ids and
- str2date(x.date, '%Y-%m-%d') <= (trans.execution_date + payment_window)
- and (not _cached(x) or _remaining(x))
- ]
- else:
- candidates = []
-
- # 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 len(candidates) > 1 or not candidates:
- ref = trans.reference.upper()
- msg = trans.message.upper()
- # The manual usage of the sales journal creates moves that
- # are not tied to invoices. Thanks to Stefan Rijnhart for
- # reporting this.
- candidates = [x for x in candidates or move_lines
- if x.invoice and has_id_match(x.invoice, ref, msg)
- and str2date(x.invoice.date_invoice, '%Y-%m-%d')
- <= (trans.execution_date + payment_window)
- and (not _cached(x) or _remaining(x))
- ]
-
- # Match on amount expected. Limit this kind of search to known
- # partners.
- if not candidates and partner_ids:
- candidates = [x for x in move_lines
- if round(abs(x.credit or x.debit), digits) ==
- round(abs(trans.transferred_amount), digits)
- and str2date(x.date, '%Y-%m-%d') <=
- (trans.execution_date + payment_window)
- and (not _cached(x) or _remaining(x))
- ]
-
- move_line = False
- if candidates and len(candidates) > 0:
- # Now a possible selection of invoices has been found, check the
- # amounts expected and received.
- #
- # TODO: currency coercing
- best = [x for x in candidates
- if round(abs(x.credit or x.debit), digits) ==
- round(abs(trans.transferred_amount), digits)
- and str2date(x.date, '%Y-%m-%d') <=
- (trans.execution_date + payment_window)
- ]
- if len(best) == 1:
- # Exact match
- move_line = best[0]
- invoice = move_line.invoice
- if _cached(move_line):
- partial = True
- expected = _remaining(move_line)
- else:
- _cache(move_line)
-
- elif len(candidates) > 1:
- # Before giving up, check cache for catching duplicate
- # transfers first
- paid = [x for x in move_lines
- if x.invoice and has_id_match(x.invoice, ref, msg)
- and str2date(x.invoice.date_invoice, '%Y-%m-%d')
- <= trans.execution_date
- and (_cached(x) and not _remaining(x))
- ]
- if paid:
- log.append(
- _('Unable to link transaction id %(trans)s '
- '(ref: %(ref)s) to invoice: '
- 'invoice %(invoice)s was already paid') % {
- 'trans': '%s.%s' % (trans.statement_id, trans.id),
- 'ref': trans.reference,
- 'invoice': eyecatcher(paid[0].invoice)
- })
- else:
- # Multiple matches
- log.append(
- _('Unable to link transaction id %(trans)s (ref: %(ref)s) to invoice: '
- '%(no_candidates)s candidates found; can\'t choose.') % {
- 'trans': '%s.%s' % (trans.statement_id, trans.id),
- 'ref': trans.reference,
- 'no_candidates': len(best) or len(candidates)
- })
- log.append(' ' +
- _('Candidates: %(candidates)s') % {
- 'candidates': ', '.join([x.invoice.number
- for x in best or candidates
- ])
- })
- move_line = False
- partial = False
-
- elif len(candidates) == 1:
- # Mismatch in amounts
- move_line = candidates[0]
- invoice = move_line.invoice
- expected = round(_sign(invoice) * invoice.residual, digits)
- partial = True
-
- trans2 = None
- if move_line and partial:
- found = round(trans.transferred_amount, digits)
- if abs(expected) == abs(found):
- partial = False
- # Last partial payment will not flag invoice paid without
- # manual assistence
- invoice_obj = self.pool.get('account.invoice')
- invoice_obj.write(cursor, uid, [invoice.id], {
- 'state': 'paid'
- })
- elif abs(expected) > abs(found):
- # Partial payment, reuse invoice
- _cache(move_line, expected - found)
- elif abs(expected) < abs(found):
- # Possible combined payments, need to split transaction to
- # verify
- _cache(move_line)
- trans2 = trans.copy()
- trans2.transferred_amount -= expected
- trans.transferred_amount = expected
- trans.id += 'a'
- trans2.id += 'b'
- # NOTE: the following is debatable. By copying the
- # eyecatcher of the invoice itself, we enhance the
- # tracability of the invoices, but we degrade the
- # tracability of the bank transactions. When debugging, it
- # is wise to disable this line.
- trans.reference = eyecatcher(move_line.invoice)
-
- if move_line:
- account_ids = [
- x.id for x in bank_account_ids
- if x.partner_id.id == move_line.partner_id.id
- ]
-
- return (
- self._get_move_info(cursor, uid, move_line,
- account_ids and account_ids[0] or False,
- partial=(partial and not trans2)
- ),
- trans2
- )
-
-
- return (False, False)
-
- def _link_canceled_debit(self, cursor, uid, trans, payment_lines,
- partner_ids, bank_account_ids, 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 _link_costs(self, cursor, uid, trans, period_id, account_info, log):
- '''
- Get or create a costs invoice for the bank and return it with
- the payment as seen in the transaction (when not already done).
- '''
- if not account_info.costs_account_id:
- return []
-
- digits = dp.get_precision('Account')(cursor)[1]
- amount = round(abs(trans.transferred_amount), digits)
- # Make sure to be able to pinpoint our costs invoice for later
- # matching
- reference = '%s.%s: %s' % (trans.statement_id, trans.id, trans.reference)
-
- # search supplier invoice
- invoice_obj = self.pool.get('account.invoice')
- invoice_ids = invoice_obj.search(cursor, uid, [
- '&',
- ('type', '=', 'in_invoice'),
- ('partner_id', '=', account_info.bank_partner_id.id),
- ('company_id', '=', account_info.company_id.id),
- ('date_invoice', '=', date2str(trans.effective_date)),
- ('reference', '=', reference),
- ('amount_total', '=', amount),
- ]
- )
- if invoice_ids and len(invoice_ids) == 1:
- invoice = invoice_obj.browse(cursor, uid, invoice_ids)[0]
- elif not invoice_ids:
- # create supplier invoice
- partner_obj = self.pool.get('res.partner')
- invoice_lines = [(0,0,dict(
- amount = 1,
- price_unit = amount,
- name = trans.message or trans.reference,
- account_id = account_info.costs_account_id.id
- ))]
- invoice_address_id = partner_obj.address_get(
- cursor, uid, [account_info.bank_partner_id.id], ['invoice']
- )
- invoice_id = invoice_obj.create(cursor, uid, dict(
- type = 'in_invoice',
- company_id = account_info.company_id.id,
- partner_id = account_info.bank_partner_id.id,
- address_invoice_id = invoice_address_id['invoice'],
- period_id = period_id,
- journal_id = account_info.invoice_journal_id.id,
- account_id = account_info.bank_partner_id.property_account_payable.id,
- date_invoice = date2str(trans.effective_date),
- reference_type = 'none',
- reference = reference,
- name = trans.reference or trans.message,
- check_total = amount,
- invoice_line = invoice_lines,
- ))
- invoice = invoice_obj.browse(cursor, uid, invoice_id)
- # Create workflow
- invoice_obj.button_compute(cursor, uid, [invoice_id],
- {'type': 'in_invoice'}, set_total=True)
- wf_service = netsvc.LocalService('workflow')
- # Move to state 'open'
- wf_service.trg_validate(uid, 'account.invoice', invoice.id,
- 'invoice_open', cursor)
-
- # return move_lines to mix with the rest
- return [x for x in invoice.move_id.line_id if x.account_id.reconcile]
-
def import_statements_file(self, cursor, uid, ids, context):
'''
Import bank statements / bank transactions file.
- This method represents the business logic, the parser modules
- represent the decoding logic.
+ This method is a wrapper for the business logic on the transaction.
+ The parser modules represent the decoding logic.
'''
banking_import = self.browse(cursor, uid, ids, context)[0]
statements_file = banking_import.file
data = base64.decodestring(statements_file)
- company_obj = self.pool.get('res.company')
user_obj = self.pool.get('res.user')
- partner_bank_obj = self.pool.get('res.partner.bank')
- journal_obj = self.pool.get('account.journal')
- move_line_obj = self.pool.get('account.move.line')
- payment_line_obj = self.pool.get('payment.line')
statement_obj = self.pool.get('account.bank.statement')
- statement_line_obj = self.pool.get('account.bank.statement.line')
statement_file_obj = self.pool.get('account.banking.imported.file')
- payment_order_obj = self.pool.get('payment.order')
- currency_obj = self.pool.get('res.currency')
+ import_transaction_obj = self.pool.get('banking.import.transaction')
# get the parser to parse the file
parser_code = banking_import.parser
@@ -499,7 +134,7 @@
user_obj.browse(cursor, uid, uid, context).company_id)
# Parse the file
- statements = parser.parse(data)
+ statements = parser.parse(cursor, data)
if any([x for x in statements if not x.is_valid()]):
raise osv.except_osv(
@@ -515,6 +150,10 @@
format = parser.name,
))
+ bank_country_code = False
+ if hasattr(parser, 'country_code'):
+ bank_country_code = parser.country_code
+
# Results
results = struct(
stat_loaded_cnt = 0,
@@ -531,48 +170,8 @@
error_accounts = {}
info = {}
imported_statement_ids = []
- linked_payments = {}
- linked_invoices = {}
- payment_lines = []
-
- if statements:
- # Get default defaults
- def_pay_account_id = company.partner_id.property_account_payable.id
- def_rec_account_id = company.partner_id.property_account_receivable.id
-
- # Get interesting journals once
- journal_ids = journal_obj.search(cursor, uid, [
- ('type', 'in', ('sale','purchase',
- 'purchase_refund','sale_refund')),
- ('company_id', '=', company.id),
- ])
- # 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)),
- ])
- if move_line_ids:
- move_lines = move_line_obj.browse(cursor, uid, move_line_ids)
- else:
- move_lines = []
-
- # 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)
-
+
+ transaction_ids = []
for statement in statements:
if statement.local_account in error_accounts:
# Don't repeat messages
@@ -640,6 +239,11 @@
continue
# Check existence of previous statement
+ # Less well defined formats can resort to a
+ # dynamically generated statement identification
+ # (e.g. a datetime string of the moment of import)
+ # and have potential duplicates flagged by the
+ # matching procedure
statement_ids = statement_obj.search(cursor, uid, [
('name', '=', statement.id),
('date', '=', date2str(statement.date)),
@@ -665,232 +269,29 @@
))
imported_statement_ids.append(statement_id)
- # move each transaction to the right period and try to match it with an
- # invoice or payment
subno = 0
- injected = []
- i = 0
- max_trans = len(statement.transactions)
- while i < max_trans:
- move_info = False
- if injected:
- # Force FIFO behavior
- transaction = injected.pop(0)
- else:
- transaction = statement.transactions[i]
- # Keep a tracer for identification of order in a statement in case
- # of missing transaction ids.
- subno += 1
-
- # Link accounting period
- period_id = get_period(self.pool, cursor, uid,
- transaction.effective_date, company,
- results.log)
- if not period_id:
- results.trans_skipped_cnt += 1
- if not injected:
- i += 1
- continue
-
- # When bank costs are part of transaction itself, split it.
- if transaction.type != bt.BANK_COSTS and transaction.provision_costs:
- # Create new transaction for bank costs
- costs = transaction.copy()
- costs.type = bt.BANK_COSTS
- costs.id = '%s-prov' % transaction.id
- costs.transferred_amount = transaction.provision_costs
- costs.remote_currency = transaction.provision_costs_currency
- costs.message = transaction.provision_costs_description
- injected.append(costs)
-
- # Remove bank costs from current transaction
- # Note that this requires that the transferred_amount
- # includes the bank costs and that the costs itself are
- # signed correctly.
- transaction.transferred_amount -= transaction.provision_costs
- transaction.provision_costs = None
- transaction.provision_costs_currency = None
- transaction.provision_costs_description = None
-
- # Allow inclusion of generated bank invoices
- if transaction.type == bt.BANK_COSTS:
- lines = self._link_costs(
- cursor, uid, transaction, period_id, account_info,
- results.log
- )
- results.bank_costs_invoice_cnt += bool(lines)
- for line in lines:
- if not [x for x in move_lines if x.id == line.id]:
- move_lines.append(line)
- partner_ids = [account_info.bank_partner_id.id]
- partner_banks = []
-
- else:
- # Link remote partner, import account when needed
- partner_banks = get_bank_accounts(
- self.pool, cursor, uid, transaction.remote_account,
- results.log, fail=True
- )
- if partner_banks:
- partner_ids = [x.partner_id.id for x in partner_banks]
- elif transaction.remote_owner:
- iban = sepa.IBAN(transaction.remote_account)
- if iban.valid:
- country_code = iban.countrycode
- elif transaction.remote_owner_country_code:
- country_code = transaction.remote_owner_country_code
- elif hasattr(parser, 'country_code') and parser.country_code:
- country_code = parser.country_code
- else:
- country_code = None
- partner_id = get_or_create_partner(
- self.pool, cursor, uid, transaction.remote_owner,
- transaction.remote_owner_address,
- transaction.remote_owner_postalcode,
- transaction.remote_owner_city,
- country_code, results.log
- )
- if transaction.remote_account:
- partner_bank_id = create_bank_account(
- self.pool, cursor, uid, partner_id,
- transaction.remote_account,
- transaction.remote_owner,
- transaction.remote_owner_address,
- transaction.remote_owner_city,
- country_code, results.log
- )
- partner_banks = partner_bank_obj.browse(
- cursor, uid, [partner_bank_id]
- )
- else:
- partner_bank_id = None
- partner_banks = []
- partner_ids = [partner_id]
- else:
- partner_ids = []
- partner_banks = []
-
- # Credit means payment... isn't it?
- if transaction.transferred_amount < 0 and payment_lines:
- # Link open payment - if any
- move_info = self._link_payment(
- cursor, uid, transaction,
- payment_lines, partner_ids,
- partner_banks, results.log, linked_payments,
- )
-
- # Second guess, invoice -> may split transaction, so beware
- if not move_info:
- # Link invoice - if any. Although bank costs are not an
- # invoice, automatic invoicing on bank costs will create
- # these, and invoice matching still has to be done.
- move_info, remainder = self._link_invoice(
- cursor, uid, transaction, move_lines, partner_ids,
- partner_banks, results.log, linked_invoices,
- )
- if remainder:
- injected.append(remainder)
-
- if not move_info:
- # Use the default settings, but allow individual partner
- # settings to overrule this. Note that you need to change
- # the internal type of these accounts to either 'payable'
- # or 'receivable' to enable usage like this.
- if transaction.transferred_amount < 0:
- if len(partner_banks) == 1:
- account_id = partner_banks[0].partner_id.property_account_payable
- if len(partner_banks) != 1 or not account_id or account_id.id == def_pay_account_id:
- account_id = account_info.default_credit_account_id
- else:
- if len(partner_banks) == 1:
- account_id = partner_banks[0].partner_id.property_account_receivable
- if len(partner_banks) != 1 or not account_id or account_id.id == def_rec_account_id:
- account_id = account_info.default_debit_account_id
- else:
- account_id = move_info.move_line.account_id
- results.trans_matched_cnt += 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,
- currency = account_info.currency_id.id,
- )
- if move_info:
- values.type = move_info.type
- values.reconcile_id = move_info.move_line.reconcile_id
- values.partner_id = move_info.partner_id
- values.partner_bank_id = move_info.partner_bank_id
- else:
- values.partner_id = values.partner_bank_id = False
- if not values.partner_id and partner_ids and len(partner_ids) == 1:
- values.partner_id = partner_ids[0]
- if not values.partner_bank_id and partner_banks and \
- len(partner_banks) == 1:
- values.partner_bank_id = partner_banks[0].id
-
- statement_line_id = statement_line_obj.create(cursor, uid, values)
- results.trans_loaded_cnt += 1
- # Only increase index when all generated transactions are
- # processed as well
- if not injected:
- i += 1
-
+ for transaction in statement.transactions:
+ subno += 1
+ if not transaction.id:
+ transaction.id = str(subno)
+ values = {}
+ for attr in transaction.__slots__ + ['type']:
+ if attr in import_transaction_obj.column_map:
+ values[import_transaction_obj.column_map[attr]] = eval('transaction.%s' % attr)
+ elif attr in import_transaction_obj._columns:
+ values[attr] = eval('transaction.%s' % attr)
+ values['statement_id'] = statement_id
+ values['bank_country_code'] = bank_country_code
+ transaction_id = import_transaction_obj.create(cursor, uid, values, context=context)
+ if transaction_id:
+ transaction_ids.append(transaction_id)
+ else:
+ osv.except_osv('Failed to create an import transaction resource','')
+
results.stat_loaded_cnt += 1
+
+ import_transaction_obj.match(cursor, uid, transaction_ids, results=results, context=context)
- 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(
- "SELECT DISTINCT o.id "
- "FROM payment_order o, payment_line l "
- "WHERE o.state = 'sent' "
- "AND o.id = l.order_id "
- "AND o.id NOT IN ("
- "SELECT DISTINCT order_id AS id "
- "FROM payment_line "
- "WHERE date_done IS NULL "
- "AND id IN (%s)"
- ")" % (','.join([str(x) for x in payment_line_ids]))
- )
- order_ids = [x[0] for x in cursor.fetchall()]
- if order_ids:
- # Use workflow logics for the orders. Recode logic from
- # account_payment, in order to increase efficiency.
- payment_order_obj.set_done(cursor, uid, order_ids,
- {'state': 'done'}
- )
- wf_service = netsvc.LocalService('workflow')
- for id in order_ids:
- wf_service.trg_validate(uid, 'payment.order', id, 'done',
- cursor
- )
-
- # Original code. Didn't take workflow logistics into account...
- #
- #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 l.id NOT IN ("
- # "SELECT DISTINCT id FROM payment_line "
- # "WHERE date_done IS NULL "
- # "AND id IN (%s)"
- # ")" % (
- # time.strftime('%Y-%m-%d'),
- # ','.join([str(x) for x in payment_line_ids])
- # )
- #)
-
report = [
'%s: %s' % (_('Total number of statements'),
results.stat_skipped_cnt + results.stat_loaded_cnt),
@@ -919,15 +320,16 @@
statement_file_obj.write(cursor, uid, import_id, dict(
state = state, log = text_log,
), context)
- if not imported_statement_ids:
+ if not imported_statement_ids or not results.trans_loaded_cnt:
# file state can be 'ready' while import state is 'error'
state = 'error'
self.write(cursor, uid, [ids[0]], dict(
import_id = import_id, log = text_log, state = state,
- statement_ids = [[6, 0, imported_statement_ids]],
+ statement_ids = [(6, 0, imported_statement_ids)],
), context)
return {
- 'name': _('Import Bank Transactions File'),
+ 'name': (state == 'ready' and _('Review Bank Statements') or
+ _('Error')),
'view_type': 'form',
'view_mode': 'form',
'view_id': False,
@@ -969,8 +371,10 @@
),
'log': fields.text('Log', readonly=True),
'state': fields.selection(
- [('init', 'init'), ('ready', 'ready'),
- ('error', 'error')],
+ [('init', 'init'),
+ ('ready', 'ready'),
+ ('error', 'error')
+ ],
'State', readonly=True),
'import_id': fields.many2one(
'account.banking.imported.file', 'Import File'),
@@ -978,6 +382,9 @@
'statement_ids': fields.many2many(
'account.bank.statement', 'rel_wiz_statements', 'wizard_id',
'statement_id', 'Imported Bank Statements'),
+ 'line_ids': fields.one2many(
+ 'banking.import.line', 'banking_import_id', 'Transactions',
+ ),
}
_defaults = {
=== modified file 'account_banking/wizard/bank_import_view.xml'
--- account_banking/wizard/bank_import_view.xml 2011-07-14 08:16:14 +0000
+++ account_banking/wizard/bank_import_view.xml 2012-01-17 10:38:36 +0000
@@ -7,7 +7,7 @@
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Import Bank Transactions File">
- <group colspan="4">
+ <group colspan="4" states="init,ready,error">
<separator colspan="4" string="Select the processing details:"/>
<field name="company" colspan="1"/>
<field name="file"/>
@@ -15,26 +15,53 @@
<field name="parser"/>
<field name="state" invisible="1"/>
</group>
- <group colspan="4" states="ready,error">
- <separator colspan="4" string="Results:"/>
- <field name="log" colspan="4" nolabel="1" width="500"/>
- </group>
- <group colspan="4" states="ready">
- <field name="statement_ids" colspan="4" nolabel="1"/>
- </group>
- <group colspan="2" states="init">
+ <notebook colspan="4">
+ <page attrs="{'invisible': [('state', '!=', 'review')]}" string="Transactions">
+ <field name="line_ids" colspan="4" nolabel="1">
+ <tree string="Transaction" min_rows="20" colors="red:duplicate;blue:reconcile_id">
+ <field name="date"/>
+ <field name="amount"/>
+ <field name="ref"/>
+ <field name="partner_bank_id"/>
+ <field name="partner_id"/>
+ <field name="account_id"/>
+ <field name="reconcile_id"/>
+ <field name="duplicate"/>
+ </tree>
+ </field>
+ </page>
+ <page attrs="{'invisible': [('state', '=', 'init'')]}" string="Log">
+ <field name="log" colspan="4" nolabel="1" width="500"/>
+ </page>
+ <page attrs="{'invisible': [('state', '!=', 'ready')]}" string="Statements">
+ <field name="statement_ids" colspan="4" nolabel="1"/>
+ </page>
+ </notebook>
+ <group colspan="2" >
<button icon="gtk-cancel"
special="cancel"
+ states="init"
string="Cancel"/>
<button icon="gtk-ok"
- string="Import"
+ string="Import"
+ states="init"
name="import_statements_file"
type="object"/>
+ <button icon="gtk-close"
+ special="cancel"
+ string="Close"
+ states="ready,error"/>
+ <button icon="gtk-close"
+ name="cancel_statement_lines"
+ type="object"
+ string="Cancel"
+ states="review"/>
+ <button icon="gtk-ok"
+ name="create_statement_lines"
+ type="object"
+ string="Confirm"
+ states="review"/>
</group>
- <button icon="gtk-close"
- special="cancel"
- string="Close"
- states="ready,error"/>
</form>
</field>
</record>
=== added file 'account_banking/wizard/banking_transaction_wizard.py'
--- account_banking/wizard/banking_transaction_wizard.py 1970-01-01 00:00:00 +0000
+++ account_banking/wizard/banking_transaction_wizard.py 2012-01-17 10:38:36 +0000
@@ -0,0 +1,337 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
+# (C) 2011 Therp BV (<http://therp.nl>).
+# (C) 2011 Smile (<http://smile.fr>).
+# 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 osv import osv, fields
+
+"""
+
+The banking transaction wizard is linked to a button in the statement line
+tree view. It allows the user to undo the duplicate flag, select between
+multiple matches or select a manual match.
+
+"""
+
+class banking_transaction_wizard(osv.osv_memory):
+ _name = 'banking.transaction.wizard'
+ _description = 'Match transaction'
+
+ def create_act_window(self, cr, uid, ids, nodestroy=True, context=None):
+ """
+ Return a popup window for this model
+ """
+ if isinstance(ids, (int, float)):
+ ids = [ids]
+ return {
+ 'name': self._description,
+ 'view_type': 'form',
+ 'view_mode': 'form',
+ 'res_model': self._name,
+ 'domain': [],
+ 'context': context,
+ 'type': 'ir.actions.act_window',
+ 'target': 'new',
+ 'res_id': ids[0],
+ 'nodestroy': nodestroy,
+ }
+
+ def trigger_match(self, cr, uid, ids, context=None):
+ """
+ Call the automatic matching routine for one or
+ more bank transactions
+ """
+ if isinstance(ids, (int, float)):
+ ids = [ids]
+ import_transaction_obj = self.pool.get('banking.import.transaction')
+ trans_id = self.read(
+ cr, uid, ids[0], ['import_transaction_id'],
+ context=context)['import_transaction_id'][0] # many2one tuple
+ import_transaction_obj.match(cr, uid, [trans_id], context=context)
+ return True
+
+ def write(self, cr, uid, ids, vals, context=None):
+ """
+ Implement a trigger to retrieve the corresponding move line
+ when the invoice_id changes
+ """
+ statement_line_obj = self.pool.get('account.bank.statement.line')
+ transaction_obj = self.pool.get('banking.import.transaction')
+
+ # The following fields get never written
+ # they are just triggers for manual matching
+ # which populates regular fields on the transaction
+ manual_invoice_id = vals.pop('manual_invoice_id', False)
+ manual_move_line_id = vals.pop('manual_move_line_id', False)
+
+ # Support for writing fields.related is still flakey:
+ # https://bugs.launchpad.net/openobject-server/+bug/915975
+ # Will do so myself.
+
+ if not vals:
+ return True
+
+ # Separate the related fields
+ transaction_vals = {}
+ wizard_vals = vals.copy()
+ for key in vals.keys():
+ field = self._columns[key]
+ if (isinstance(field, fields.related) and
+ field._arg[0] == 'import_transaction_id'):
+ transaction_vals[field._arg[1]] = vals[key]
+ del wizard_vals[key]
+
+ # write the related fields on the transaction model
+ for wizard in self.read(
+ cr, uid, ids, ['import_transaction_id'], context=context):
+ if wizard['import_transaction_id']:
+ transaction_obj.write(
+ cr, uid, wizard['import_transaction_id'][0],
+ transaction_vals, context=context)
+
+ # write other fields to the wizard model
+ res = super(banking_transaction_wizard, self).write(
+ cr, uid, ids, wizard_vals, context=context)
+
+ # End of workaround for lp:915975
+
+ """ Process the logic of the written values """
+
+ # An invoice is selected from multiple candidates
+ if vals and 'invoice_id' in vals:
+ for wiz in self.browse(cr, uid, ids, context=context):
+ if (wiz.import_transaction_id.match_type == 'invoice' and
+ wiz.import_transaction_id.invoice_id):
+ # the current value might apply
+ if (wiz.move_line_id and wiz.move_line_id.invoice and
+ wiz.move_line_id.invoice.id == wiz.invoice_id.id):
+ found = True
+ continue
+ # Otherwise, retrieve the move line for this invoice
+ # Given the arity of the relation, there is are always
+ # multiple possibilities but the move lines here are
+ # prefiltered for having account_id.type payable/receivable
+ # and the regular invoice workflow should only come up with
+ # one of those only.
+ for move_line in wiz.import_transaction_id.move_line_ids:
+ if (move_line.invoice.id ==
+ wiz.import_transaction_id.invoice_id.id):
+ transaction_obj.write(
+ cr, uid, wiz.import_transaction_id.id,
+ { 'move_line_id': move_line.id, }, context=context)
+ statement_line_obj.write(
+ cr, uid, wiz.import_transaction_id.statement_line_id.id,
+ { 'partner_id': move_line.invoice.partner_id.id,
+ 'account_id': move_line.account_id.id,
+ }, context=context)
+ found = True
+ break
+ # Cannot match the invoice
+ if not found:
+ # transaction_obj.write(
+ # cr, uid, wiz.import_transaction_id.id,
+ # { 'invoice_id': False, }, context=context)
+ osv.except_osv(
+ _("No entry found for the selected invoice"),
+ _("No entry found for the selected invoice. " +
+ "Try manual reconciliation."))
+
+ if manual_move_line_id or manual_invoice_id:
+ move_line_obj = self.pool.get('account.move.line')
+ invoice_obj = self.pool.get('account.invoice')
+ statement_line_obj = self.pool.get('account.bank.statement.line')
+ for wiz in self.browse(
+ cr, uid, ids, context=context):
+ invoice_ids = False
+ move_line_id = False
+ move_line_ids = False
+ invoice_id = manual_invoice_id
+ if invoice_id:
+ invoice = invoice_obj.browse(
+ cr, uid, manual_invoice_id, context=context)
+ if invoice.move_id:
+ for line in invoice.move_id.line_id:
+ if line.account_id.type in ('receivable', 'payable'):
+ move_line_id = line.id
+ break
+ if not move_line_id:
+ osv.except_osv(
+ _("Cannot select for reconcilion"),
+ _("No entry found for the selected invoice. "))
+ else:
+ move_line_id = manual_move_line_id
+ move_line = move_line_obj.read(
+ cr, uid, move_line_id, ['invoice'], context=context)
+ invoice_id = (move_line['invoice'] and
+ move_line['invoice'][0])
+ vals = {
+ 'move_line_id': move_line_id,
+ 'move_line_ids': [(6, 0, [move_line_id])],
+ 'invoice_id': invoice_id,
+ 'invoice_ids': [(6, 0, invoice_id and
+ [invoice_id] or [])],
+ 'match_type': 'manual',
+ }
+ transaction_obj.clear_and_write(
+ cr, uid, wiz.import_transaction_id.id,
+ vals, context=context)
+ st_line_vals = {
+ 'account_id': move_line_obj.read(
+ cr, uid, move_line_id,
+ ['account_id'], context=context)['account_id'][0],
+ }
+ if invoice_id:
+ st_line_vals['partner_id'] = invoice_obj.read(
+ cr, uid, invoice_id,
+ ['partner_id'], context=context)['partner_id'][0]
+ statement_line_obj.write(
+ cr, uid, wiz.import_transaction_id.statement_line_id.id,
+ st_line_vals, context=context)
+ return res
+
+ def trigger_write(self, cr, uid, ids, context=None):
+ """
+ Just a button that triggers a write.
+ """
+ return True
+
+ def disable_match(self, cr, uid, ids, context=None):
+ """
+ Clear manual and automatic match information
+ """
+ if isinstance(ids, (int, float)):
+ ids = [ids]
+# self.write(cr, uid, ids,
+# {'manual_invoice_id': False,
+# 'manual_move_line_id': False,
+# }, context=context)
+ wizs = self.read(
+ cr, uid, ids, ['import_transaction_id'], context=context)
+ trans_ids = [x['import_transaction_id'][0] for x in wizs
+ if x['import_transaction_id']]
+ return self.pool.get('banking.import.transaction').clear_and_write(
+ cr, uid, trans_ids, context=context)
+
+ def reverse_duplicate(self, cr, uid, ids, context=None):
+ if isinstance(ids, (int, float)):
+ ids = [ids]
+ transaction_obj = self.pool.get('banking.import.transaction')
+ for wiz in self.read(
+ cr, uid, ids, ['duplicate', 'import_transaction_id'],
+ context=context):
+ transaction_obj.write(
+ cr, uid, wiz['import_transaction_id'][0],
+ {'duplicate': not wiz['duplicate']}, context=context)
+ return True
+
+ def _get_default_match_type(self, cr, uid, context=None):
+ """
+ Take initial value for the match type from the statement line
+ """
+ res = False
+ if context and 'statement_line_id' in context:
+ res = self.pool.get('account.bank.statement.line').read(
+ cr, uid, context['statement_line_id'],
+ ['match_type'], context=context)['match_type']
+ return res
+
+ def button_done(self, cr, uid, ids, context=None):
+ return {'nodestroy': False, 'type': 'ir.actions.act_window_close'}
+
+ _defaults = {
+# 'match_type': _get_default_match_type,
+ }
+
+ _columns = {
+ 'name': fields.char('Name', size=64),
+ 'statement_line_id': fields.many2one(
+ 'account.bank.statement.line', 'Statement line',
+ ),
+ 'amount': fields.related(
+ 'statement_line_id', 'amount', type='float',
+ string="Amount", readonly=True),
+ 'date': fields.related(
+ 'statement_line_id', 'date', type='date',
+ string="Date", readonly=True),
+ 'ref': fields.related(
+ 'statement_line_id', 'ref', type='char', size=32,
+ string="Reference", readonly=True),
+ 'partner_id': fields.related(
+ 'statement_line_id', 'partner_id',
+ type='many2one', relation='res.partner',
+ string="Partner", readonly=True),
+ 'import_transaction_id': fields.related(
+ 'statement_line_id', 'import_transaction_id',
+ string="Import transaction",
+ type='many2one', relation='banking.import.transaction'),
+ 'residual': fields.related(
+ 'import_transaction_id', 'residual', type='float',
+ string='Residual', readonly=True),
+ 'writeoff_account_id': fields.related(
+ 'import_transaction_id', 'writeoff_account_id',
+ type='many2one', relation='account.account',
+ string='Write-off account'),
+ 'writeoff_journal_id': fields.related(
+ 'import_transaction_id', 'writeoff_journal_id',
+ type='many2one', relation='account.journal',
+ string='Write-off journal'),
+ 'payment_line_id': fields.related(
+ 'import_transaction_id', 'payment_line_id', string="Matching payment or storno",
+ type='many2one', relation='payment.line', readonly=True),
+ 'payment_order_ids': fields.related(
+ 'import_transaction_id', 'payment_order_ids', string="Matching payment orders",
+ type='many2many', relation='payment.order'),
+ 'payment_order_id': fields.related(
+ 'import_transaction_id', 'payment_order_id', string="Payment order to reconcile",
+ type='many2one', relation='payment.order'),
+ 'invoice_ids': fields.related(
+ 'import_transaction_id', 'invoice_ids', string="Matching invoices",
+ type='many2many', relation='account.invoice'),
+ 'invoice_id': fields.related(
+ 'import_transaction_id', 'invoice_id', string="Invoice to reconcile",
+ type='many2one', relation='account.invoice'),
+ 'move_line_ids': fields.related(
+ 'import_transaction_id', 'move_line_ids', string="Entry lines",
+ type='many2many', relation='account.move.line'),
+ 'move_line_id': fields.related(
+ 'import_transaction_id', 'move_line_id', string="Entry line",
+ type='many2one', relation='account.move.line'),
+ 'duplicate': fields.related(
+ 'import_transaction_id', 'duplicate', string='Flagged as duplicate',
+ type='boolean'),
+ 'match_multi': fields.related(
+ 'import_transaction_id', 'match_multi',
+ type="boolean", string='Multiple matches'),
+ 'match_type': fields.related(
+ 'import_transaction_id', 'match_type',
+ type="char", size=16, string='Match type', readonly=True),
+ 'manual_invoice_id': fields.many2one(
+ 'account.invoice', 'Match this invoice',
+ domain=[('reconciled', '=', False)]),
+ 'manual_move_line_id': fields.many2one(
+ 'account.move.line', 'Or match this entry',
+ domain=[('account_id.reconcile', '=', True),
+ ('reconcile_id', '=', False)],
+ #'manual_payment_order_id': fields.many2one(
+ # 'payment.order', "Payment order to reconcile"),
+ ),
+ }
+banking_transaction_wizard()
+
=== added file 'account_banking/wizard/banking_transaction_wizard.xml'
--- account_banking/wizard/banking_transaction_wizard.xml 1970-01-01 00:00:00 +0000
+++ account_banking/wizard/banking_transaction_wizard.xml 2012-01-17 10:38:36 +0000
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+ <record model="ir.ui.view" id="transaction_wizard_first">
+ <field name="name">transaction.wizard.first</field>
+ <field name="type">form</field>
+ <field name="model">banking.transaction.wizard</field>
+ <field name="arch" type="xml">
+ <form string="Match transaction">
+ <!-- fields used for form logic -->
+ <field name="payment_order_ids" invisible="True"/>
+ <field name="invoice_ids" invisible="True"/>
+ <field name="move_line_ids" invisible="True"/>
+ <field name="match_multi" invisible="True"/>
+ <field name="duplicate" invisible="True"/>
+ <group colspan="2" col="2">
+ <group colspan="2" col="4">
+ <separator string="Transaction data"/>
+ <field name="date"/>
+ <field name="amount"/>
+ <field name="ref"/>
+ <field name="partner_id"/>
+ </group>
+
+ <!-- Duplicate flagging -->
+ <group colspan="2" col="2" attrs="{'invisible': [('duplicate', '=', False)]}">
+ <separator string="Duplicate flag"/>
+ <label colspan="2" string="This bank transfer was marked as a duplicate. You can either confirm that this is not the case, or remove the bank transfer from the system."/>
+ <newline/>
+ <button colspan="1"
+ name="reverse_duplicate"
+ type="object"
+ string="Remove duplicate flag"/>
+ </group>
+
+ <!-- (semi-) automatic matching and selection -->
+
+ <group colspan="2" col="2">
+
+ <separator string="Current match" colspan="2"/>
+ <field name="match_type"/>
+ <group attrs="{'invisible': [('match_multi', '=', False)]}" colspan="2" col="2">
+ <separator string="Multiple matches" colspan="2"/>
+ <label colspan="2" string="Multiple matches were found for this bank transfer. You must pick one of the matches or select a match manually below." />
+ </group>
+ <field name='payment_line_id'
+ attrs="{'invisible': [('match_type', '!=', 'storno'),('match_type', '!=', 'payment')]}"
+ />
+ <group attrs="{'readonly': [('match_multi', '!=' True)]}">
+ <!-- show if we have an invoice type match (but the user may need to select from multiple options)
+ or whenever there is an invoice_id (e.g. in case of a manual match)
+ -->
+ <field name='invoice_id'
+ attrs="{'readonly': [('match_multi', '=', False)], 'invisible': [('match_type', '!=', 'invoice'),('invoice_id', '=', False)]}"
+ domain="[('id', 'in', invoice_ids[0][2])]"
+ />
+ <!-- show if we have a move type match or a manual match without an invoice_id
+ -->
+ <field name='move_line_id'
+ attrs="{'readonly': [('match_multi', '=', False)], 'invisible': [('match_type', '!=', 'move'),('invoice_id', '=', False)]}"
+ domain="[('id', 'in', move_line_ids[0][2])]"
+ />
+ <field name='payment_order_id'
+ attrs="{'readonly': [('match_multi', '=', False)], 'invisible': [('match_type', '!=', 'payment_order')]}"
+ domain="[('id', 'in', payment_order_ids[0][2])]"
+ />
+ </group>
+ <button colspan="1"
+ name="trigger_write"
+ type="object"
+ attrs="{'invisible': [('match_multi', '=', False)]}"
+ string="Select"/>
+ <newline/>
+
+ <!-- residual and write off -->
+
+ <group attrs="{'invisible': [('residual', '=', False)]}" colspan="2" col="2">
+ <separator string="Residual write-off"/>
+ <label string="If the amount exceeds the match, you must set a write-off account and journal for the residual of this reconciliation. If the amount is smaller than the match, this is optional. If you do not set a write-off account in this case, the result will be a partial reconciliation." colspan="2"/>
+ <field name="residual"/>
+ <!-- no luck with these lt/gt calculations in attrs -->
+ <field name="writeoff_account_id"
+ attrs="{'required': ['|','&',('residual', '<', 0),('amount', '>', 0),'&',('residual', '>', 0),('amount', '<', 0)]}"/>
+ <field name="writeoff_journal_id"
+ attrs="{'required': ['|','&',('residual', '<', 0),('amount', '>', 0),'&',('residual', '>', 0),('amount', '<', 0)]}"/>
+ <button colspan="1"
+ name="trigger_write"
+ type="object"
+ string="Set write-off account"/>
+ </group>
+
+ <!-- Redo automatic match -->
+
+ <separator string="System match"/>
+ <label string="You can let the system try to match this bank statement line again after you have made any changes in the database (for instance, add an invoice or a bank account)." colspan="2"/>
+ <newline/>
+ <button colspan="1"
+ name="trigger_match"
+ type="object"
+ string="Match again"/>
+ <!-- Manual selection -->
+
+ <separator string="Manual match" colspan="2"/>
+ <field name="manual_invoice_id"/>
+ <field name="manual_move_line_id"/>
+ <newline/>
+ <button colspan="1"
+ name="trigger_write"
+ type="object"
+ string="Match"/>
+ </group>
+
+ <group attrs="{'invisible': [('match_type', '==', False)]}" colspan="2" col="2">
+ <separator string="Disable reconciliation"/>
+ <label string="You can disable the reconciliation of this bank transfer" colspan="2"/>
+ <newline/>
+ <button colspan="1"
+ name="disable_match"
+ type="object"
+ string="Disable reconciliation"/>
+ </group>
+ <group colspan="2">
+ <separator/>
+ <button icon="gtk-ok" string="Done" special="cancel"/>
+ </group>
+ </group>
+ </form>
+ </field>
+ </record>
+ </data>
+</openerp>
=== modified file 'account_banking/wizard/banktools.py'
--- account_banking/wizard/banktools.py 2011-04-29 18:04:10 +0000
+++ account_banking/wizard/banktools.py 2012-01-17 10:38:36 +0000
@@ -136,11 +136,12 @@
)
country_id = country_ids and country_ids[0] or False
filter.append(('country_id', '=', country_id))
- if address:
+ # disable for now. Apparently, address is an array of lines.
+ if address and False:
if len(address) >= 1:
- filter.append(('street', 'ilike', addres[0]))
+ filter.append(('street', 'ilike', address[0]))
if len(address) > 1:
- filter.append(('street2', 'ilike', addres[1]))
+ filter.append(('street2', 'ilike', address[1]))
if city:
filter.append(('city', 'ilike', city))
if postal_code:
@@ -159,8 +160,6 @@
if (not country_code) or not country_id:
user = pool.get('res.user').browse(cursor, uid, uid)
country_id = (
- user.company_id and
- user.company_id.partner_id and
user.company_id.partner_id.country and
user.company_id.partner_id.country.id or
False
=== added directory 'account_banking/workflow'
=== added file 'account_banking/workflow/account_invoice.xml'
--- account_banking/workflow/account_invoice.xml 1970-01-01 00:00:00 +0000
+++ account_banking/workflow/account_invoice.xml 2012-01-17 10:38:36 +0000
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+ <!--
+ Transition to reopening the invoice, triggered by
+ cancelling a bank transaction with which the invoice
+ was paid.
+ the existing workflow contains a similar step
+ but without the test on being reconciled
+ -->
+ <record id="paid_to_open" model="workflow.transition">
+ <field name="act_from" ref="account.act_paid"/>
+ <field name="act_to" ref="account.act_open"/>
+ <field name="condition">test_undo_paid()</field>
+ <field name="signal">undo_paid</field>
+ </record>
+ </data>
+</openerp>
=== modified file 'account_banking_fi_patu/patu.py'
--- account_banking_fi_patu/patu.py 2010-06-30 07:11:08 +0000
+++ account_banking_fi_patu/patu.py 2012-01-17 10:38:36 +0000
@@ -118,7 +118,7 @@
will parse all statements in a file and import them to OpenERP
''')
- def parse(self, data):
+ def parse(self, cr, data):
result = []
stmnt = None
patuparser = PatuParser()
=== modified file 'account_banking_nl_abnamro/abnamro.py'
--- account_banking_nl_abnamro/abnamro.py 2011-04-27 10:16:40 +0000
+++ account_banking_nl_abnamro/abnamro.py 2012-01-17 10:38:36 +0000
@@ -261,26 +261,27 @@
each file covers a period of two weeks.
''')
- def parse(self, data):
+ def parse(self, cr, data):
result = []
stmnt = None
lines = data.split('\n')
# Transaction lines are not numbered, so keep a tracer
subno = 0
+ statement_id = False
for line in csv.reader(lines, delimiter = '\t', quoting=csv.QUOTE_NONE):
# Skip empty (last) lines
if not line:
continue
subno += 1
msg = transaction_message(line, subno)
- if stmnt and stmnt.id != msg.statement_id:
- result.append(stmnt)
- stmnt = None
- subno = 0
- if not stmnt:
+ if not statement_id:
+ statement_id = self.get_unique_statement_id(
+ cr, msg.effective_date.strftime('%Yw%W'))
+ msg.statement_id = statement_id
+ if stmnt:
+ stmnt.import_transaction(msg)
+ else:
stmnt = statement(msg)
- else:
- stmnt.import_transaction(msg)
result.append(stmnt)
return result
=== modified file 'account_banking_nl_clieop/__terp__.py'
--- account_banking_nl_clieop/__terp__.py 2011-07-24 18:58:32 +0000
+++ account_banking_nl_clieop/__terp__.py 2012-01-17 10:38:36 +0000
@@ -25,7 +25,7 @@
##############################################################################
{
'name': 'Account Banking NL ClieOp',
- 'version': '0.63',
+ 'version': '0.92',
'license': 'GPL-3',
'author': 'EduSense BV',
'website': 'http://www.edusense.nl',
=== modified file 'account_banking_nl_clieop/data/banking_export_clieop.xml'
--- account_banking_nl_clieop/data/banking_export_clieop.xml 2011-07-21 11:30:59 +0000
+++ account_banking_nl_clieop/data/banking_export_clieop.xml 2012-01-17 10:38:36 +0000
@@ -8,6 +8,7 @@
eval="[(6,0,[ref('base_iban.bank_iban'),ref('base.bank_normal'),])]" />
<field name="ir_model_id"
ref="account_banking_nl_clieop.model_banking_export_clieop_wizard"/>
+ <field name="payment_order_type">debit</field>
</record>
<record model="payment.mode.type" id="account_banking_nl_clieop.export_clieop_pay">
@@ -17,6 +18,7 @@
eval="[(6,0,[ref('base_iban.bank_iban'),ref('base.bank_normal'),])]" />
<field name="ir_model_id"
ref="account_banking_nl_clieop.model_banking_export_clieop_wizard"/>
+ <field name="payment_order_type">payment</field>
</record>
<record model="payment.mode.type" id="account_banking_nl_clieop.export_clieop_sal">
@@ -26,6 +28,7 @@
eval="[(6,0,[ref('base_iban.bank_iban'),ref('base.bank_normal'),])]" />
<field name="ir_model_id"
ref="account_banking_nl_clieop.model_banking_export_clieop_wizard"/>
+ <field name="payment_order_type">payment</field>
</record>
</data>
</openerp>
=== added directory 'account_banking_nl_clieop/migrations/0.64'
=== added file 'account_banking_nl_clieop/migrations/0.64/post-set-payment-order-type.py'
--- account_banking_nl_clieop/migrations/0.64/post-set-payment-order-type.py 1970-01-01 00:00:00 +0000
+++ account_banking_nl_clieop/migrations/0.64/post-set-payment-order-type.py 2012-01-17 10:38:36 +0000
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (C) 2011 Therp BV (<http://therp.nl>)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU 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/>.
+#
+##############################################################################
+
+""" r64: introduction of the payment_mode_type in order to support of debit orders
+"""
+__name__ = "payment.mode.type:: set payment_mode_type to 'debit' for Clieop incasso export"
+
+def migrate(cr, version):
+ cr.execute ("UPDATE payment_mode_type"
+ " SET payment_order_type = 'debit'"
+ " FROM ir_model_data "
+ " WHERE res_id = payment_mode_type.id"
+ " AND module = 'account_banking_nl_clieop'"
+ " AND model = 'payment.mode.type'"
+ " AND ir_model_data.name = 'export_clieop_inc'"
+ )
=== modified file 'account_banking_nl_clieop/wizard/export_clieop.py'
--- account_banking_nl_clieop/wizard/export_clieop.py 2011-07-21 11:30:59 +0000
+++ account_banking_nl_clieop/wizard/export_clieop.py 2012-01-17 10:38:36 +0000
@@ -23,6 +23,7 @@
from datetime import datetime, date, timedelta
from osv import osv, fields
from tools.translate import _
+import netsvc
from account_banking import sepa
import clieop
@@ -365,8 +366,9 @@
clieop_file = clieop_obj.write(
cursor, uid, clieop_export['file_id'].id, {'state':'sent'}
)
- payment_order_obj.action_sent(
- cursor, uid, [x.id for x in clieop_export['payment_order_ids']])
+ wf_service = netsvc.LocalService('workflow')
+ for order in clieop_export['payment_order_ids']:
+ wf_service.trg_validate(uid, 'payment.order', order.id, 'sent', cursor)
return {'type': 'ir.actions.act_window_close'}
banking_export_clieop_wizard()
=== modified file 'account_banking_nl_girotel/girotel.py'
--- account_banking_nl_girotel/girotel.py 2011-04-21 19:03:58 +0000
+++ account_banking_nl_girotel/girotel.py 2012-01-17 10:38:36 +0000
@@ -312,7 +312,7 @@
The Dutch Girotel - Kommagescheiden format is basicly a MS Excel CSV format.
''')
- def parse(self, data):
+ def parse(self, cr, data):
result = []
stmnt = None
dialect = csv.excel()
=== added directory 'account_banking_nl_ing'
=== added file 'account_banking_nl_ing/__init__.py'
--- account_banking_nl_ing/__init__.py 1970-01-01 00:00:00 +0000
+++ account_banking_nl_ing/__init__.py 2012-01-17 10:38:36 +0000
@@ -0,0 +1,4 @@
+# -*- encoding: utf-8 -*-
+import ing
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
=== added file 'account_banking_nl_ing/__openerp__.py'
--- account_banking_nl_ing/__openerp__.py 1970-01-01 00:00:00 +0000
+++ account_banking_nl_ing/__openerp__.py 2012-01-17 10:38:36 +0000
@@ -0,0 +1,61 @@
+##############################################################################
+#
+# Copyright (C) 2011 Therp BV (<http://therp.nl>)
+# (C) 2011 Smile BV (<http://smile.fr>)
+#
+# Based on account-banking
+# (C) 2009 - 2011 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
+# or Therp 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': 'ING (NL) Bank Statements Import',
+ 'version': '0.1.92',
+ 'license': 'GPL-3',
+ 'author': 'Smile / Therp BV / EduSense BV',
+ 'website': 'https://launchpad.net/banking-addons',
+ 'category': 'Banking addons',
+ 'depends': ['account_banking'],
+ 'init_xml': [],
+ 'update_xml': [
+ ],
+ 'demo_xml': [],
+ 'description': '''
+Module to import Dutch ING bank format transaction files (CSV format).
+
+As the ING bank does not provide detailed specification concerning possible
+values and their meaning for the fields in the CSV file format, the statements
+are parsed according to an educated guess based on incomplete information.
+You can contact the banking-addons developers through their launchpad page and
+help improve the performance of this import filter on
+https://launchpad.net/banking-addons.
+
+Note that imported bank transfers are organized in statements covering periods
+of one week, even if the imported files cover a different period.
+
+This modules contains no logic, just an import filter for account_banking.
+ ''',
+ 'active': False,
+ 'installable': True,
+}
=== added file 'account_banking_nl_ing/__terp__.py'
--- account_banking_nl_ing/__terp__.py 1970-01-01 00:00:00 +0000
+++ account_banking_nl_ing/__terp__.py 2012-01-17 10:38:36 +0000
@@ -0,0 +1,58 @@
+##############################################################################
+#
+# Copyright (C) 2009 - 2011 EduSense BV (<http://www.edusense.nl>)
+# and Therp BV (<http://therp.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
+# or Therp 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': 'ING (NL) Bank Statements Import',
+ 'version': '0.1.89',
+ 'license': 'GPL-3',
+ 'author': 'Smile / Therp BV / EduSense BV',
+ 'website': 'https://launchpad.net/banking-addons',
+ 'category': 'Banking addons',
+ 'depends': ['account_banking'],
+ 'init_xml': [],
+ 'update_xml': [
+ ],
+ 'demo_xml': [],
+ 'description': '''
+
+Module to import Dutch ING bank format transation files (CSV format).
+
+As the ING bank does not provide detailed specification concerning possible
+values and their meaning for the fields in the CSV file format, the statements
+are parsed according to an educated guess based on incomplete information.
+You can contact the banking-addons developers through their launchpad page and
+help improve the performance of this import filter on
+https://launchpad.net/banking-addons.
+
+Note that imported bank transfers are organized in statements covering periods
+of one week, even if the imported files cover a different period.
+
+This modules contains no logic, just an import filter for account_banking.
+ ''',
+ 'active': False,
+ 'installable': True,
+}
=== added directory 'account_banking_nl_ing/i18n'
=== added file 'account_banking_nl_ing/i18n/nl.po'
--- account_banking_nl_ing/i18n/nl.po 1970-01-01 00:00:00 +0000
+++ account_banking_nl_ing/i18n/nl.po 2012-01-17 10:38:36 +0000
@@ -0,0 +1,36 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+# * account_banking_nl_ing
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 6.0.3\n"
+"Report-Msgid-Bugs-To: support@xxxxxxxxxxx\n"
+"POT-Creation-Date: 2011-12-26 07:52+0000\n"
+"PO-Revision-Date: 2011-12-26 07:52+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_nl_ing
+#: model:ir.module.module,shortdesc:account_banking_nl_ing.module_meta_information
+msgid "ING (NL) Bank Statements Import"
+msgstr "ING (NL) Bankafschriften importeren "
+
+#. module: account_banking_nl_ing
+#: model:ir.module.module,description:account_banking_nl_ing.module_meta_information
+msgid "Module to import Dutch ING bank format transaction files (CSV format).As the ING bank does not provide detailed specification concerning possiblevalues and their meaning for the fields in the CSV file format, the statementsare parsed according to an educated guess based on incomplete information.You can contact the banking-addons developers through their launchpad page andhelp improve the performance of this import filter onhttps://launchpad.net/banking-addons.Note that imported bank transfers are organized in statements covering periodsof one week, even if the imported files cover a different period.This modules contains no logic, just an import filter for account_banking. "
+msgstr "Module voor het importeren van bankafschriften van de Nederlandse ING bank in\n"
+"CSV formaat.\n\nGezien het feit dat de ING geen enkele vorm van specificaties\n"
+"beschikbaar stelt, is de importroutine samengesteld op basis van \n"
+"voorbeeldbestanden, en kan volledige dekking niet worden gegarandeerd. U\n"
+"kunt, om de werking van de importroutine te helpen verbeteren, de ontwikkelaars\n"
+"van het banking-addons project uit de OpenERP community benaderen via het\n"
+"Launchpadproject. Zie https://launchpad.net/banking-addons.\n"
+"\n"
+"Deze module bevat geen bedrijfslogica, maar moet gebruikt worden als\n"
+"filtermodule voor de module 'account_banking'\n"
+
=== added file 'account_banking_nl_ing/ing.py'
--- account_banking_nl_ing/ing.py 1970-01-01 00:00:00 +0000
+++ account_banking_nl_ing/ing.py 2012-01-17 10:38:36 +0000
@@ -0,0 +1,230 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+# Copyright (C) 2011 Smile (<http://smile.fr>).
+# Copyright (C) 2011 Therp BV (<http://therp.nl>).
+#
+# Based on the multibank module by EduSense BV
+# 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 datetime import datetime
+from account_banking.parsers import models
+from account_banking.parsers.convert import str2date
+from account_banking.sepa import postalcode
+from tools.translate import _
+
+import re
+import csv
+
+__all__ = ['parser']
+
+bt = models.mem_bank_transaction
+
+"""
+First line states the legend
+"Datum","Naam / Omschrijving","Rekening","Tegenrekening","Code","Af Bij","Bedrag (EUR)","MutatieSoort","Mededelingen
+
+"""
+
+class transaction_message(object):
+ '''
+ A auxiliary class to validate and coerce read values
+ '''
+ attrnames = [
+ 'date', 'remote_owner', 'local_account', 'remote_account',
+ 'transfer_type', 'debcred', 'transferred_amount',
+ 'transfer_type_verbose', 'message'
+ ]
+
+ def __init__(self, values, subno):
+ '''
+ Initialize own dict with attributes and coerce values to right type
+ '''
+ if len(self.attrnames) != len(values):
+ raise ValueError(
+ _('Invalid transaction line: expected %d columns, found %d')
+ % (len(self.attrnames), len(values)))
+ self.__dict__.update(dict(zip(self.attrnames, values)))
+ # for lack of a standardized locale function to parse amounts
+ self.transferred_amount = float(
+ re.sub(',', '.', self.transferred_amount))
+ if self.debcred == 'Af':
+ self.transferred_amount = -self.transferred_amount
+ self.execution_date = self.effective_date = str2date(self.date, '%Y%m%d')
+ self.statement_id = '' #self.effective_date.strftime('%Yw%W')
+ self.id = str(subno).zfill(4)
+ self.reference = ''
+ # Normalize basic account numbers
+ self.remote_account = self.remote_account.replace('.', '').zfill(10)
+ self.local_account = self.local_account.replace('.', '').zfill(10)
+
+class transaction(models.mem_bank_transaction):
+ '''
+ Implementation of transaction communication class for account_banking.
+ '''
+ attrnames = ['local_account', 'remote_account',
+ 'remote_owner', 'transferred_amount',
+ 'execution_date', 'effective_date', 'transfer_type',
+ 'id', 'reference', 'statement_id', 'message',
+ ]
+
+ """
+ Presumably the same transaction types occur in the MT940 format of ING.
+ From www.ing.nl/Images/MT940_Technische_handleiding_tcm7-69020.pdf
+
+ """
+ type_map = {
+
+ 'AC': bt.ORDER, # Acceptgiro
+ 'BA': bt.PAYMENT_TERMINAL, # Betaalautomaattransactie
+ 'CH': bt.ORDER, # Cheque
+ 'DV': bt.ORDER, # Diversen
+ 'FL': bt.BANK_TERMINAL, # Filiaalboeking, concernboeking
+ 'GF': bt.ORDER, # Telefonisch bankieren
+ 'GM': bt.BANK_TERMINAL, # Geldautomaat
+ 'GT': bt.ORDER, # Internetbankieren
+ 'IC': bt.DIRECT_DEBIT, # Incasso
+ 'OV': bt.ORDER, # Overschrijving
+ 'PK': bt.BANK_TERMINAL, # Opname kantoor
+ 'PO': bt.ORDER, # Periodieke overschrijving
+ 'ST': bt.BANK_TERMINAL, # Storting (eigen rekening of derde)
+ 'VZ': bt.ORDER, # Verzamelbetaling
+ 'NO': bt.STORNO, # Storno
+ }
+
+ # global expression for matching storno references
+ ref_expr = re.compile('REF[\*:]([0-9A-Z-z_-]+)')
+
+ def __init__(self, line, *args, **kwargs):
+ '''
+ Initialize own dict with read values.
+ '''
+ super(transaction, self).__init__(*args, **kwargs)
+ # Copy attributes from auxiliary class to self.
+ for attr in self.attrnames:
+ setattr(self, attr, getattr(line, attr))
+ # self.message = ''
+ # Decompose structured messages
+ self.parse_message()
+ # Adaptations to direct debit orders ands stornos
+ if self.transfer_type == 'DV' and self.transferred_amount < 0:
+ res = self.ref_expr.search(self.remote_owner)
+ if res:
+ self.transfer_type = 'NO'
+ self.reference = res.group(1)
+ self.remote_owner = False
+ else:
+ res = self.ref_expr.search(self.message)
+ if res:
+ self.transfer_type = 'NO'
+ self.reference = res.group(1)
+ if self.transfer_type == 'IC':
+ if self.transferred_amount > 0:
+ self.reference = self.remote_owner
+ else:
+ self.transfer_type = 'NO'
+ self.message = self.remote_owner + self.message
+ res = self.ref_expr.search(self.message)
+ if res:
+ self.reference = res.group(1)
+ self.storno_retry = True
+ self.remote_owner = False
+
+ def is_valid(self):
+ if not self.error_message:
+ if not self.transferred_amount:
+ self.error_message = "No transferred amount"
+ elif not self.execution_date:
+ self.error_message = "No execution date"
+ elif not self.remote_account and self.transfer_type not in [
+ 'BA', 'FL', 'GM', 'IC', 'PK', 'ST'
+ ]:
+ self.error_message = (
+ "No remote account for transaction type %s" %
+ self.transfer_type)
+ return not self.error_message
+
+ def parse_message(self):
+ '''
+ Parse structured message parts into appropriate attributes.
+ No processing done here for Triodos, maybe later.
+ '''
+
+class statement(models.mem_bank_statement):
+ '''
+ Implementation of bank_statement communication class of account_banking
+ '''
+ def __init__(self, msg, *args, **kwargs):
+ '''
+ Set decent start values based on first transaction read
+ '''
+ super(statement, self).__init__(*args, **kwargs)
+ self.id = msg.statement_id
+ self.local_account = msg.local_account
+ self.date = str2date(msg.date, '%Y%m%d')
+ self.start_balance = self.end_balance = 0 # msg.start_balance
+ self.import_transaction(msg)
+
+ def import_transaction(self, msg):
+ '''
+ Import a transaction and keep some house holding in the mean time.
+ '''
+ trans = transaction(msg)
+ self.end_balance += trans.transferred_amount
+ self.transactions.append(trans)
+
+class parser(models.parser):
+ code = 'ING'
+ country_code = 'NL'
+ name = _('ING Bank')
+ doc = _('''\
+The Dutch ING format is basicly a MS Excel CSV format. It is specifically
+distinct from the Dutch multibank format. Transactions are not tied to Bank
+Statements.
+''')
+
+ def parse(self, cr, data):
+ result = []
+ stmnt = None
+ dialect = csv.excel()
+ dialect.quotechar = '"'
+ dialect.delimiter = ','
+ lines = data.split('\n')
+ # Transaction lines are not numbered, so keep a tracer
+ subno = 0
+ statement_id = False
+ for line in csv.reader(lines, dialect=dialect):
+ # Skip empty (last) lines and header line
+ if not line or line[0] == 'Datum':
+ continue
+ subno += 1
+ msg = transaction_message(line, subno)
+ if not statement_id:
+ statement_id = self.get_unique_statement_id(
+ cr, msg.effective_date.strftime('%Yw%W'))
+ msg.statement_id = statement_id
+ if stmnt:
+ stmnt.import_transaction(msg)
+ else:
+ stmnt = statement(msg)
+ result.append(stmnt)
+ return result
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
=== modified file 'account_banking_nl_multibank/multibank.py'
--- account_banking_nl_multibank/multibank.py 2011-04-27 05:26:55 +0000
+++ account_banking_nl_multibank/multibank.py 2012-01-17 10:38:36 +0000
@@ -296,7 +296,7 @@
to Bank Statements.
''')
- def parse(self, data):
+ def parse(self, cr, data):
result = []
stmnt = None
dialect = csv.excel()
=== modified file 'account_banking_nl_triodos/__openerp__.py'
--- account_banking_nl_triodos/__openerp__.py 2011-04-26 21:00:12 +0000
+++ account_banking_nl_triodos/__openerp__.py 2012-01-17 10:38:36 +0000
@@ -27,7 +27,7 @@
##############################################################################
{
'name': 'Triodos (NL) Bank Statements Import',
- 'version': '0.1',
+ 'version': '0.92',
'license': 'GPL-3',
'author': 'Therp BV / EduSense BV',
'website': 'https://launchpad.net/account-banking',
=== modified file 'account_banking_nl_triodos/triodos.py'
--- account_banking_nl_triodos/triodos.py 2011-07-24 18:58:32 +0000
+++ account_banking_nl_triodos/triodos.py 2012-01-17 10:38:36 +0000
@@ -28,6 +28,7 @@
Every transaction is bound to a Bank Statement. As such, this module generates
Bank Statements along with Bank Transactions.
'''
+from datetime import datetime
from account_banking.parsers import models
from account_banking.parsers.convert import str2date
from account_banking.sepa import postalcode
@@ -65,8 +66,7 @@
self.transferred_amount = -self.transferred_amount
self.execution_date = str2date(self.date, '%d-%m-%Y')
self.effective_date = str2date(self.date, '%d-%m-%Y')
- # Set statement_id based on week number
- self.statement_id = self.effective_date.strftime('%Yw%W')
+ self.statement_id = '' # self.effective_date.strftime('%Yw%W') # Set statement_id based on week number
self.id = str(subno).zfill(4)
# Normalize basic account numbers
self.remote_account = self.remote_account.replace('.', '').zfill(10)
@@ -187,7 +187,7 @@
Statements.
''')
- def parse(self, data):
+ def parse(self, cr, data):
result = []
stmnt = None
dialect = csv.excel()
@@ -196,20 +196,22 @@
lines = data.split('\n')
# Transaction lines are not numbered, so keep a tracer
subno = 0
+ # fixed statement id based on import timestamp
+ statement_id = False
for line in csv.reader(lines, dialect=dialect):
# Skip empty (last) lines
if not line:
continue
subno += 1
msg = transaction_message(line, subno)
- if stmnt and stmnt.id != msg.statement_id:
- result.append(stmnt)
- stmnt = None
- subno = 0
- if not stmnt:
+ if not statement_id:
+ statement_id = self.get_unique_statement_id(
+ cr, msg.effective_date.strftime('%Yw%W'))
+ msg.statement_id = statement_id
+ if stmnt:
+ stmnt.import_transaction(msg)
+ else:
stmnt = statement(msg)
- else:
- stmnt.import_transaction(msg)
result.append(stmnt)
return result
=== added directory 'account_direct_debit'
=== added file 'account_direct_debit/__init__.py'
--- account_direct_debit/__init__.py 1970-01-01 00:00:00 +0000
+++ account_direct_debit/__init__.py 2012-01-17 10:38:36 +0000
@@ -0,0 +1,1 @@
+import model
=== added file 'account_direct_debit/__openerp__.py'
--- account_direct_debit/__openerp__.py 1970-01-01 00:00:00 +0000
+++ account_direct_debit/__openerp__.py 2012-01-17 10:38:36 +0000
@@ -0,0 +1,62 @@
+##############################################################################
+#
+# Copyright (C) 2011 Therp BV (<http://therp.nl>).
+# Copyright (C) 2011 Smile (<http://smile.fr>).
+# 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': 'Direct Debit',
+ 'version': '6.0.1.98',
+ 'license': 'GPL-3',
+ 'author': 'Therp BV / Smile',
+ 'website': 'https://launchpad.net/banking-addons',
+ 'category': 'Banking addons',
+ 'depends': ['account_banking'],
+ 'init_xml': [],
+ 'update_xml': [
+ 'view/account_payment.xml',
+ 'view/account_invoice.xml',
+ 'workflow/account_invoice.xml',
+ 'workflow/account_payment.xml',
+ 'data/account_payment_term.xml',
+ ],
+ 'demo_xml': [],
+ 'description': '''
+This module adds support for direct debit orders, analogous to payment orders.
+A new entry in the Accounting/Payment menu allow you to create a direct debit
+order that helps you to select any customer invoices for you to collect.
+
+This module explicitely implements direct debit orders as applicable
+in the Netherlands. Debit orders are advanced in total by the bank.
+Amounts that cannot be debited or are canceled by account owners are
+credited afterwards. Such a creditation is called a storno. This style of
+direct debit order may not apply to your country.
+
+This module depends on and is part of the banking addons for OpenERP. This set
+of modules helps you to provide support for communications with your local
+banking institutions. The banking addons are a continuation of Account Banking
+Framework by Edusense BV. See https://launchpad.net/banking-addons.
+ ''',
+ 'active': False,
+ 'installable': True,
+}
=== added directory 'account_direct_debit/data'
=== added file 'account_direct_debit/data/account_payment_term.xml'
--- account_direct_debit/data/account_payment_term.xml 1970-01-01 00:00:00 +0000
+++ account_direct_debit/data/account_payment_term.xml 2012-01-17 10:38:36 +0000
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data noupdate="1">
+ <record id="payment_term_direct_debit" model="account.payment.term">
+ <field name="name">Direct debit</field>
+ <field name="note">Direct debit in 14 days</field>
+ </record>
+ <record id="payment_term_line_direct_debit" model="account.payment.term.line">
+ <field name="name">Direct debit in 14 days</field>
+ <field name="value">balance</field>
+ <field eval="14" name="days"/>
+ <field eval="0" name="days2"/>
+ <field eval="payment_term_direct_debit" name="payment_id"/>
+ </record>
+ </data>
+</openerp>
=== added directory 'account_direct_debit/i18n'
=== added file 'account_direct_debit/i18n/nl.po'
--- account_direct_debit/i18n/nl.po 1970-01-01 00:00:00 +0000
+++ account_direct_debit/i18n/nl.po 2012-01-17 10:38:36 +0000
@@ -0,0 +1,210 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+# * account_direct_debit
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 6.0.3\n"
+"Report-Msgid-Bugs-To: stefan@xxxxxxxx\n"
+"POT-Creation-Date: 2011-12-25 12:11+0000\n"
+"PO-Revision-Date: 2011-12-25 12:11+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_direct_debit
+#: model:account.payment.term,note:account_direct_debit.payment_term_direct_debit
+msgid "Direct debit in 14 days"
+msgstr "Het bedrag wordt binnen 14 dagen van uw rekening afgeschreven."
+
+#. module: account_direct_debit
+#: field:account.move.line,payment_term_id:0
+msgid "Select by invoice payment term"
+msgstr "Selecteer op betalingsconditie"
+
+#. module: account_direct_debit
+#: model:ir.model,name:account_direct_debit.model_payment_order
+msgid "Payment Order"
+msgstr "Betalingsopdracht"
+
+#. module: account_direct_debit
+#: view:payment.order:0
+msgid "Select Invoices to Collect"
+msgstr "Selecteer openstaande posten ter incasso"
+
+#. module: account_direct_debit
+#: help:payment.mode,payment_term_ids:0
+msgid "Limit selected invoices to invoices with these payment terms"
+msgstr "De te selecteren facturen worden voorgeselecteerd op deze betalingscondities"
+
+#. module: account_direct_debit
+#: model:ir.model,name:account_direct_debit.model_payment_line
+msgid "Payment Line"
+msgstr "Betaalregel"
+
+#. module: account_direct_debit
+#: model:ir.model,name:account_direct_debit.model_payment_mode
+msgid "Payment Mode"
+msgstr "Betaalmodus"
+
+#. module: account_direct_debit
+#: view:account.invoice:0
+msgid "Debit Denied"
+msgstr "Incasso geweigerd"
+
+#. module: account_direct_debit
+#: help:payment.mode,transfer_account_id:0
+msgid "Pay off lines in sent orders with a move on this account. For debit type modes only. You can only select accounts of type regular that are marked for reconciliation"
+msgstr "Betaal op het moment van versturen de openstaande posten met een boeking op deze rekening. Alleen van toepassing op een incassomodus. Alleen rekeningen van type 'normaal' kunnen hier worden geselecteerd, die ook voorzien zijn van kemnerk 'afletteren'.
+
+#. module: account_direct_debit
+#: field:account.move.line,invoice_state:0
+msgid "Select by invoice state"
+msgstr "Zoekveld factuurstatus"
+
+#. module: account_direct_debit
+#: model:ir.model,name:account_direct_debit.model_account_move_line
+msgid "Journal Items"
+msgstr "Dagboekregels"
+
+#. module: account_direct_debit
+#: constraint:account.move.line:0
+msgid "Company must be same for its related account and period."
+msgstr "Het bedrijf moet gelijk zijn voor het rekeningnummer en de periode."
+
+#. module: account_direct_debit
+#: field:payment.mode,payment_term_ids:0
+msgid "Payment terms"
+msgstr "Betalingscondities"
+
+#. module: account_direct_debit
+#: view:account.invoice:0
+msgid "Debit denied"
+msgstr "Incasso geweigerd"
+
+#. module: account_direct_debit
+#: model:ir.actions.act_window,name:account_direct_debit.action_debit_order_tree
+#: model:ir.ui.menu,name:account_direct_debit.menu_action_debit_order_form
+msgid "Direct Debit Orders"
+msgstr "Incasso-opdrachten"
+
+#. module: account_direct_debit
+#: model:account.payment.term,name:account_direct_debit.payment_term_direct_debit
+msgid "Direct debit"
+msgstr "Automatische incasso"
+
+#. module: account_direct_debit
+#: constraint:account.move.line:0
+msgid "You can not create move line on closed account."
+msgstr "U kunt geen boekingsregel creëren op een gesloten rekening"
+
+#. module: account_direct_debit
+#: model:ir.module.module,description:account_direct_debit.module_meta_information
+msgid "\n"
+"This module adds support for direct debit orders, analogous to payment orders.\n"
+"A new entry in the Accounting/Payment menu allow you to create a direct debit order that\n"
+"helps you to select any customer invoices for you to collect.\n"
+"\n"
+"This module depends on and is part of the banking addons for OpenERP. This set of\n"
+"modules helps you to provide support for communications with your local banking\n"
+"institutions. See https://launchpad.net/banking-addons.\n"
+"\n"
+"The banking addons are a continuation of Account Banking Framework by Edusense BV.\n"
+"See https://launchpad.net/account-banking.\n"
+" "
+msgstr "\n"
+"Deze module bevat ondersteuning voor het uitvoeren van incasso-opdrachten, vergelijkbaar\n"
+"met betalingsopdrachten. Een nieuw menuitem wordt toegevoegd onder Financieel/Betalingen,\n"
+"dat u in de gelenheid stelt om een incasso-opdracht aan te maken en openstaande\n"
+"verkoopfacturen te selecteren.\n"
+"\n"
+"Deze module hangt samen en maakt onderdeel uit van de Banking addons voor OpenERP.\n"
+"Deze modules vormen de basis voor het uitwisselen van bestanden met uw eigen bank.\n"
+"Zie https://launchpad.net/banking-addons.\n"
+"\n"
+"Banking addons zijn een voortzetting van Account Banking Framework van Edusense BV.\n"
+"Zie voor het originele project https://launchpad.net/account-banking.\n"
+" "
+
+#. module: account_direct_debit
+#: sql_constraint:account.move.line:0
+msgid "Wrong credit or debit value in accounting entry !"
+msgstr "Verkeerde debet of credit waarde in boekingsregel!"
+
+#. module: account_direct_debit
+#: model:ir.model,name:account_direct_debit.model_payment_order_create
+msgid "payment.order.create"
+msgstr "payment.order.create"
+
+#. module: account_direct_debit
+#: field:payment.mode,transfer_account_id:0
+msgid "Transfer account"
+msgstr "Tussenrekening"
+
+#. module: account_direct_debit
+#: field:payment.line,storno:0
+msgid "Storno"
+msgstr "Storno"
+
+#. module: account_direct_debit
+#: help:payment.line,storno:0
+msgid "If this is true, the debit order has been canceled by the bank or by the customer"
+msgstr "In dit geval is de incasso-opdracht geanulleerd door de bank of de klant"
+
+#. module: account_direct_debit
+#: model:ir.module.module,shortdesc:account_direct_debit.module_meta_information
+msgid "Direct Debit"
+msgstr "Automatische incasso"
+
+#. module: account_direct_debit
+#: model:ir.actions.act_window,help:account_direct_debit.action_debit_order_tree
+msgid "A debit order is a debit request from your company to collect customer invoices. Here you can register all debit orders that should be done, keep track of all debit orders and mention the invoice reference and the partner the withdrawal should be done for."
+msgstr "Een incasso-opdracht is een betalingsopdracht van uw bedrijfa aan de bank om de bedragen voor openstaande verkoopfacturen van de bankrekeningen van uw klanten af te schrijven. Hier kunt u alle incasso-opdrachten samenstellen, beheren en hun status nagaan."
+
+#. module: account_direct_debit
+#: field:account.move.line,amount_to_receive:0
+msgid "Amount to receive"
+msgstr "Te ontvangen bedrag"
+
+#. module: account_direct_debit
+#: sql_constraint:payment.line:0
+msgid "The payment line name must be unique!"
+msgstr "De betaalregelnaam moet uniek zijn!"
+
+#. module: account_direct_debit
+#: field:payment.line,debit_move_line_id:0
+msgid "Debit move line"
+msgstr "Debetboeking"
+
+#. module: account_direct_debit
+#: field:payment.mode,transfer_journal_id:0
+msgid "Transfer journal"
+msgstr "Dagboek voor bedragen onderweg"
+
+#. module: account_direct_debit
+#: help:payment.mode,transfer_journal_id:0
+msgid "Journal to write payment entries when confirming a debit order of this mode"
+msgstr "Dagboek voor het inschrijven van de boeking 'bedrag onderweg', op moment van versturen van de incasso-opdracht"
+
+#. module: account_direct_debit
+#: view:account.invoice:0
+msgid "Re-Open"
+msgstr "Open opnieuw"
+
+#. module: account_direct_debit
+#: model:ir.model,name:account_direct_debit.model_account_invoice
+msgid "Invoice"
+msgstr "Factuur"
+
+#. module: account_direct_debit
+#: help:payment.line,debit_move_line_id:0
+msgid "Move line through which the debit order pays the invoice"
+msgstr "Dagboekregel waarmee de incasso-opdracht de factuur voldoet"
+
+#. module: account_direct_debit
+#: constraint:account.move.line:0
+msgid "You can not create move line on view account."
+msgstr "U kunt geen dagboekregel schrijven op een overzichtsrekening."
=== added directory 'account_direct_debit/model'
=== added file 'account_direct_debit/model/__init__.py'
--- account_direct_debit/model/__init__.py 1970-01-01 00:00:00 +0000
+++ account_direct_debit/model/__init__.py 2012-01-17 10:38:36 +0000
@@ -0,0 +1,3 @@
+import account_payment
+import account_move_line
+import account_invoice
=== added file 'account_direct_debit/model/account_invoice.py'
--- account_direct_debit/model/account_invoice.py 1970-01-01 00:00:00 +0000
+++ account_direct_debit/model/account_invoice.py 2012-01-17 10:38:36 +0000
@@ -0,0 +1,143 @@
+# -*- coding: utf-8 -*-
+from osv import osv, fields
+from tools.translate import _
+
+"""
+This module adds support for Direct debit orders as applicable
+in the Netherlands. Debit orders are advanced in total by the bank.
+Amounts that cannot be debited or are canceled by account owners are
+credited afterwards. Such a creditation is called a storno.
+
+Invoice workflow:
+
+1 the sale leads to
+ 1300 Debtors 100
+ 8000 Sales 100
+
+Balance:
+ Debtors 2000 |
+ Sales | 2000
+
+2 an external booking takes place
+ 1100 Bank 100
+ 1300 Debtors 100
+ This booking is reconciled with [1]
+ The invoice gets set to state 'paid', and 'reconciled' = True
+
+Balance:
+ Debtors 1900 |
+ Bank 100 |
+ Sales | 2000
+
+This module implements the following diversion:
+
+2a the invoice is included in a direct debit order. When the order is
+ confirmed, a move is created per invoice:
+
+ 2000 Transfer account 100 |
+ 1300 Debtors | 100
+ Reconciliation takes place between 1 and 2a.
+ The invoice gets set to state 'paid', and 'reconciled' = True
+
+Balance:
+ Debtors 0 |
+ Transfer account 2000 |
+ Bank 0 |
+ Sales | 2000
+
+3a the direct debit order is booked on the bank account
+
+Balance:
+ 1100 Bank 2000 |
+ 2000 Transfer account | 2000
+ Reconciliation takes place between 3a and 2a
+
+Balance:
+ Debtors 0 |
+ Transfer account 0 |
+ Bank 2000 |
+ Sales | 2000
+
+4 a storno from invoice [1] triggers a new booking on the bank account
+ 1300 Debtors 100 |
+ 1100 Bank | 100
+
+Balance:
+ Debtors 100 |
+ Transfer account 0 |
+ Bank 1900 |
+ Sales | 2000
+
+ The reconciliation of 2a is undone. The booking of 2a is reconciled
+ with the booking of 4 instead.
+ The payment line attribute 'storno' is set to True and the invoice
+ state is no longer 'paid'.
+
+Two cases need to be distinguisted:
+ 1) If the storno is a manual storno from the partner, the invoice is set to
+ state 'debit_denied', with 'reconciled' = False
+ This module implements this option by allowing the bank module to call
+
+ netsvc.LocalService("workflow").trg_validate(
+ uid, 'account.invoice', ids, 'debit_denied', cr)
+
+ 2) If the storno is an error generated by the bank (assumingly non-fatal),
+ the invoice is reopened for the next debit run. This is a call to existing
+
+ netsvc.LocalService("workflow").trg_validate(
+ uid, 'account.invoice', ids, 'open_test', cr)
+
+ Should also be adding a log entry on the invoice for tracing purposes
+
+ self._log_event(cr, uid, ids, -1.0, 'Debit denied')
+
+ If not for that funny comment
+ "#TODO: implement messages system" in account/invoice.py
+
+ Repeating non-fatal fatal errors need to be dealt with manually by checking
+ open invoices with a matured invoice- or due date.
+"""
+
+class account_invoice(osv.osv):
+ _inherit = "account.invoice"
+
+ def __init__(self, pool, cr):
+ """
+ Adding a state to the hardcoded state list of the inherited
+ model. The alternative is duplicating the field definition
+ in columns but only one module can do that!
+
+ Maybe apply a similar trick when overriding the buttons' 'states' attributes
+ in the form view, manipulating the xml in fields_view_get().
+ """
+ super(account_invoice, self).__init__(pool, cr)
+ invoice_obj = pool.get('account.invoice')
+ invoice_obj._columns['state'].selection.append(
+ ('debit_denied', 'Debit denied'))
+
+ def action_debit_denied(self, cr, uid, ids, context=None):
+ for invoice_id in ids:
+ if self.test_paid(cr, uid, [invoice_id], context):
+ number = self.read(
+ cr, uid, invoice_id, ['number'], context=context)['number']
+ raise osv.except_osv(
+ _('Error !'),
+ _('You cannot set invoice \'%s\' to state \'debit denied\', ' +
+ 'as it is still reconciled.') % number)
+ self.write(cr, uid, ids, {'state':'debit_denied'}, context=context)
+ for inv_id, name in self.name_get(cr, uid, ids, context=context):
+ message = _("Invoice '%s': direct debit is denied.") % name
+ self.log(cr, uid, inv_id, message)
+ return True
+
+ def test_undo_debit_denied(self, cr, uid, ids, context=None):
+ """
+ Called from the workflow. Used to unset paid state on
+ invoices that were paid with bank transfers which are being cancelled
+ """
+ for invoice in self.read(cr, uid, ids, ['reconciled'], context):
+ if not invoice['reconciled']:
+ return False
+ return True
+
+account_invoice()
=== added file 'account_direct_debit/model/account_move_line.py'
--- account_direct_debit/model/account_move_line.py 1970-01-01 00:00:00 +0000
+++ account_direct_debit/model/account_move_line.py 2012-01-17 10:38:36 +0000
@@ -0,0 +1,155 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+# This module additional (C) 2011 Therp BV (<http://therp.nl>).
+# (C) 2011 Smile Benelux (<http://smile.fr>).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from operator import itemgetter
+from osv import fields, osv
+from tools.translate import _
+
+class account_move_line(osv.osv):
+ _inherit = "account.move.line"
+
+ def amount_to_receive(self, cr, uid, ids, name, arg={}, context=None):
+ """
+ Return the amount still to receive regarding all the debit orders
+ (excepting canceled orders).
+ This is the reverse from amount_to_pay() in
+ account_payment/account_move_line.py
+ """
+ if not ids:
+ return {}
+ cr.execute("""SELECT ml.id,
+ CASE WHEN ml.amount_currency > 0
+ THEN ml.amount_currency
+ ELSE ml.debit
+ END -
+ (SELECT coalesce(sum(amount_currency),0)
+ FROM payment_line pl
+ INNER JOIN payment_order po
+ ON (pl.order_id = po.id)
+ WHERE move_line_id = ml.id
+ AND pl.storno is false
+ AND po.state != 'cancel') AS amount
+ FROM account_move_line ml
+ WHERE id IN %s""", (tuple(ids),))
+ r = dict(cr.fetchall())
+ return r
+
+ def _to_receive_search(self, cr, uid, obj, name, args, context=None):
+ if not args:
+ return []
+ line_obj = self.pool.get('account.move.line')
+ query = line_obj._query_get(cr, uid, context={})
+ where = ' and '.join(map(lambda x: '''(SELECT
+ CASE WHEN l.amount_currency > 0
+ THEN l.amount_currency
+ ELSE l.debit
+ END - coalesce(sum(pl.amount_currency), 0)
+ FROM payment_line pl
+ INNER JOIN payment_order po ON (pl.order_id = po.id)
+ WHERE move_line_id = l.id
+ AND pl.storno is false
+ AND po.state != 'cancel'
+ ) %(operator)s %%s ''' % {'operator': x[1]}, args))
+ sql_args = tuple(map(itemgetter(2), args))
+
+ cr.execute(('''SELECT id
+ FROM account_move_line l
+ WHERE account_id IN (select id
+ FROM account_account
+ WHERE type=%s AND active)
+ AND reconcile_id IS null
+ AND debit > 0
+ AND ''' + where + ' and ' + query), ('receivable',)+sql_args )
+
+ res = cr.fetchall()
+ if not res:
+ return [('id', '=', '0')]
+ return [('id', 'in', map(lambda x:x[0], res))]
+
+ def _dummy(self, cr, user, ids, name, arg, context=None):
+ res = {}
+ if ids:
+ res = dict([(x.id, False) for x in ids])
+ return res
+
+ def _invoice_payment_term_id_search(
+ self, cr, uid, obj, name, args, context=None):
+ """
+ Allow to search move lines associated with an invoice with
+ a particular payment term
+ """
+ if not args:
+ return []
+ invoice_obj = self.pool.get('account.invoice')
+ invoice_ids = invoice_obj.search(
+ cr, uid, [('payment_term', args[0][1], args[0][2])],
+ context=context)
+ operator = 'in' # (args[0][1] not in ['in', '=', '==', 'like', 'ilike']
+ # and 'not in' or 'in')
+ if not invoice_ids:
+ return [('id', operator, [])]
+ cr.execute('SELECT l.id ' \
+ 'FROM account_move_line l, account_invoice i ' \
+ 'WHERE l.move_id = i.move_id AND i.id in %s', (tuple(invoice_ids),))
+ res = cr.fetchall()
+ if not res:
+ return [('id', '=', False)]
+ return [('id', operator, [x[0] for x in res])]
+
+ def _invoice_state_search(self, cr, uid, obj, name, args, context=None):
+ if not args:
+ return []
+ invoice_obj = self.pool.get('account.invoice')
+ invoice_ids = invoice_obj.search(
+ cr, uid, [('state', args[0][1], args[0][2])],
+ context=context)
+ operator = 'in' # (args[0][1] not in ['in', '=', '==', 'like', 'ilike']
+ # and 'not in' or 'in')
+ if not invoice_ids:
+ return [('id', operator, [])]
+ cr.execute('SELECT l.id ' \
+ 'FROM account_move_line l, account_invoice i ' \
+ 'WHERE l.move_id = i.move_id AND i.id in %s', (tuple(invoice_ids),))
+ res = cr.fetchall()
+ if not res:
+ return [('id', '=', False)]
+ return [('id', operator, [x[0] for x in res])]
+
+ _columns = {
+ 'amount_to_receive': fields.function(
+ amount_to_receive, method=True,
+ type='float', string='Amount to receive',
+ fnct_search=_to_receive_search),
+ 'payment_term_id': fields.function(
+ _dummy, method=True,
+ string='Select by invoice payment term',
+ type='many2one', relation='account.payment.term',
+ fnct_search=_invoice_payment_term_id_search),
+ 'invoice_state': fields.function(
+ _dummy, method=True,
+ string='Select by invoice state',
+ type='char', size=24,
+ fnct_search=_invoice_state_search),
+ }
+
+account_move_line()
=== added file 'account_direct_debit/model/account_payment.py'
--- account_direct_debit/model/account_payment.py 1970-01-01 00:00:00 +0000
+++ account_direct_debit/model/account_payment.py 2012-01-17 10:38:36 +0000
@@ -0,0 +1,478 @@
+# -*- coding: utf-8 -*-
+import time
+from osv import osv, fields
+import netsvc
+from tools.translate import _
+
+class payment_mode(osv.osv):
+ _inherit = 'payment.mode'
+ _columns = {
+ 'transfer_account_id': fields.many2one(
+ 'account.account', 'Transfer account',
+ domain=[('type', '=', 'other'),
+ ('reconcile', '=', True)],
+ help=('Pay off lines in sent orders with a ' +
+ 'move on this account. For debit type modes only. ' +
+ 'You can only select accounts of type regular that ' +
+ 'are marked for reconciliation'),
+ ),
+ 'transfer_journal_id': fields.many2one(
+ 'account.journal', 'Transfer journal',
+ help=('Journal to write payment entries when confirming ' +
+ 'a debit order of this mode'),
+ ),
+ 'payment_term_ids': fields.many2many(
+ 'account.payment.term', 'account_payment_order_terms_rel',
+ 'mode_id', 'term_id', 'Payment terms',
+ help=('Limit selected invoices to invoices with these payment ' +
+ 'terms')
+ ),
+ }
+payment_mode()
+
+class payment_order(osv.osv):
+ _inherit = 'payment.order'
+
+ def fields_view_get(self, cr, user, view_id=None, view_type='form',
+ context=None, toolbar=False, submenu=False):
+ """
+ We use the same form for payment and debit orders, but they
+ are accessible through different menu items. The user should only
+ be allowed to select a payment mode that applies to the type of order
+ i.e. payment or debit.
+
+ A pretty awful workaround is needed for the fact that no dynamic
+ domain is possible on the selection widget. This domain is encoded
+ in the context of the menu item.
+ """
+ if not context:
+ context = {}
+ res = super(payment_order, self).fields_view_get(
+ cr, user, view_id, view_type, context, toolbar, submenu)
+ if context.get('search_payment_order_type', False) and view_type == 'form':
+ if 'mode' in res['fields'] and 'selection' in res['fields']['mode']:
+ mode_obj = self.pool.get('payment.mode')
+ domain = ['|', ('type', '=', False), ('type.payment_order_type', '=',
+ context['search_payment_order_type'])]
+ # the magic is in the value of the selection
+ res['fields']['mode']['selection'] = mode_obj._name_search(
+ cr, user, args=domain)
+ # also update the domain
+ res['fields']['mode']['domain'] = domain
+ return res
+
+ def debit_reconcile_transfer(self, cr, uid, payment_order_id,
+ amount, currency, context=None):
+ """
+ During import of bank statements, create the reconcile on the transfer
+ account containing all the open move lines on the transfer account.
+ """
+ move_line_obj = self.pool.get('account.move.line')
+ order = self.browse(cr, uid, payment_order_id, context)
+ line_ids = []
+ reconcile_id = False
+ for order_line in order.line_ids:
+ for line in order_line.debit_move_line_id.move_id.line_id:
+ if line.account_id.type == 'other' and not line.reconcile_id:
+ line_ids.append(line.id)
+ if self.pool.get('res.currency').is_zero(
+ cr, uid, currency,
+ move_line_obj.get_balance(cr, uid, line_ids) - amount):
+ reconcile_id = self.pool.get('account.move.reconcile').create(
+ cr, uid,
+ {'type': 'auto', 'line_id': [(6, 0, line_ids)]},
+ context)
+ # set direct debit order to finished state
+ wf_service = netsvc.LocalService('workflow')
+ wf_service.trg_validate(
+ uid, 'payment.order', payment_order_id, 'done', cr)
+ return reconcile_id
+
+ def debit_unreconcile_transfer(self, cr, uid, payment_order_id, reconcile_id,
+ amount, currency, context=None):
+ """
+ Due to a cancelled bank statements import, unreconcile the move on
+ the transfer account. Delegate the conditions to the workflow.
+ Raise on failure for rollback.
+ """
+ self.pool.get('account.move.reconcile').unlink(
+ cr, uid, reconcile_id, context=context)
+ wkf_ok = netsvc.LocalService('workflow').trg_validate(
+ uid, 'payment.order', payment_order_id, 'undo_done', cr)
+ if not wkf_ok:
+ raise osv.except_osv(
+ _("Cannot unreconcile"),
+ _("Cannot unreconcile debit order: "+
+ "Workflow will not allow it."))
+
+ return True
+
+ def test_undo_done(self, cr, uid, ids, context=None):
+ """
+ Called from the workflow. Used to unset done state on
+ payment orders that were reconciled with bank transfers
+ which are being cancelled
+ """
+ for order in self.browse(cr, uid, ids, context=context):
+ if order.payment_order_type == 'debit':
+ for line in order.line_ids:
+ if line.storno:
+ return False
+ else:
+ # TODO: define conditions for 'payment' orders
+ return False
+ return True
+
+ def action_sent(self, cr, uid, ids, context=None):
+ """
+ Create the moves that pay off the move lines from
+ the debit order. This happens when the debit order file is
+ generated.
+ """
+ res = super(payment_order, self).action_sent(
+ cr, uid, ids, context)
+
+ account_move_obj = self.pool.get('account.move')
+ account_move_line_obj = self.pool.get('account.move.line')
+ payment_line_obj = self.pool.get('payment.line')
+ for order in self.browse(cr, uid, ids, context=context):
+ if order.payment_order_type != 'debit':
+ continue
+ for line in order.line_ids:
+ # basic checks
+ if not line.move_line_id:
+ raise osv.except_osv(
+ _('Error'),
+ _('No move line provided for line %s') % line.name)
+ if line.move_line_id.reconcile_id:
+ raise osv.except_osv(
+ _('Error'),
+ _('Move line %s has already been paid/reconciled') %
+ line.move_line_id.name
+ )
+
+ move_id = account_move_obj.create(cr, uid, {
+ 'journal_id': order.mode.transfer_journal_id.id,
+ 'name': 'Debit order %s' % line.move_line_id.move_id.name,
+ 'reference': 'DEB%s' % line.move_line_id.move_id.name,
+ }, context=context)
+
+ # TODO: take multicurrency into account
+
+ # create the debit move line on the transfer account
+ vals = {
+ 'name': 'Debit order for %s' % (
+ line.move_line_id.invoice and
+ line.move_line_id.invoice.number or
+ line.move_line_id.name),
+ 'move_id': move_id,
+ 'partner_id': line.partner_id.id,
+ 'account_id': order.mode.transfer_account_id.id,
+ 'credit': 0.0,
+ 'debit': line.amount,
+ 'date': time.strftime('%Y-%m-%d'),
+ }
+ transfer_move_line_id = account_move_line_obj.create(
+ cr, uid, vals, context=context)
+
+ # create the debit move line on the receivable account
+ vals.update({
+ 'account_id': line.move_line_id.account_id.id,
+ 'credit': line.amount,
+ 'debit': 0.0,
+ })
+ reconcile_move_line_id = account_move_line_obj.create(
+ cr, uid, vals, context=context)
+
+ # register the debit move line on the payment line
+ # and call reconciliation on it
+ payment_line_obj.write(
+ cr, uid, line.id,
+ {'debit_move_line_id': reconcile_move_line_id},
+ context=context)
+
+ payment_line_obj.debit_reconcile(
+ cr, uid, line.id, context=context)
+ account_move_obj.post(cr, uid, [move_id], context=context)
+ return res
+
+payment_order()
+
+class payment_line(osv.osv):
+ _inherit = 'payment.line'
+
+ def debit_storno(self, cr, uid, payment_line_id, amount,
+ currency, storno_retry=True, context=None):
+ """
+ The processing of a storno is triggered by a debit
+ transfer on one of the company's bank accounts.
+ This method offers to re-reconcile the original debit
+ payment. For this purpose, we have registered that
+ payment move on the payment line.
+
+ Return the (now incomplete) reconcile id. The caller MUST
+ re-reconcile this reconcile with the bank transfer and
+ re-open the associated invoice.
+
+ :param payment_line_id: the single payment line id
+ :param amount: the (signed) amount debited from the bank account
+ :param currency: the bank account's currency *browse object*
+ :param boolean storno_retry: when True, attempt to reopen the invoice, \
+ set the invoice to 'Debit denied' otherwise.
+ :return: an incomplete reconcile for the caller to fill
+ :rtype: database id of an account.move.reconcile resource.
+ """
+
+ move_line_obj = self.pool.get('account.move.line')
+ reconcile_obj = self.pool.get('account.move.reconcile')
+ line = self.browse(cr, uid, payment_line_id)
+ reconcile_id = False
+ if (line.debit_move_line_id and not line.storno and
+ self.pool.get('res.currency').is_zero(
+ cr, uid, currency, (
+ (line.debit_move_line_id.credit or 0.0) -
+ (line.debit_move_line_id.debit or 0.0) + amount))):
+ # Two different cases, full and partial
+ # Both cases differ subtly in the procedure to follow
+ # Needs refractoring, but why is this not in the OpenERP API?
+ # Actually, given the nature of a direct debit order and storno,
+ # we should not need to take partial into account on the side of
+ # the debit_move_line.
+ if line.debit_move_line_id.reconcile_partial_id:
+ reconcile_id = line.debit_move_line_id.reconcile_partial_id.id
+ attribute = 'reconcile_partial_id'
+ if len(line.debit_move_line_id.reconcile_id.line_partial_ids) == 2:
+ # reuse the simple reconcile for the storno transfer
+ reconcile_obj.write(
+ cr, uid, reconcile_id, {
+ 'line_id': [(6, 0, line.debit_move_line_id.id)],
+ 'line_partial_ids': [(6, 0, [])],
+ }, context=context)
+ else:
+ # split up the original reconcile in a partial one
+ # and a new one for reconciling the storno transfer
+ reconcile_obj.write(
+ cr, uid, reconcile_id, {
+ 'line_partial_ids': [(3, line.debit_move_line_id.id)],
+ }, context=context)
+ reconcile_id = reconcile_obj.create(
+ cr, uid, {
+ 'type': 'auto',
+ 'line_id': [(6, 0, line.debit_move_line_id.id)],
+ }, context=context)
+ elif line.debit_move_line_id.reconcile_id:
+ reconcile_id = line.debit_move_line_id.reconcile_id.id
+ if len(line.debit_move_line_id.reconcile_id.line_id) == 2:
+ # reuse the simple reconcile for the storno transfer
+ reconcile_obj.write(
+ cr, uid, reconcile_id, {
+ 'line_id': [(6, 0, [line.debit_move_line_id.id])]
+ }, context=context)
+ else:
+ # split up the original reconcile in a partial one
+ # and a new one for reconciling the storno transfer
+ partial_ids = [
+ x.id for x in line.debit_move_line_id.reconcile_id.line_id
+ if x.id != line.debit_move_line_id.id
+ ]
+ reconcile_obj.write(
+ cr, uid, reconcile_id, {
+ 'line_partial_ids': [(6, 0, partial_ids)],
+ 'line_id': [(6, 0, [])],
+ }, context=context)
+ reconcile_id = reconcile_obj.create(
+ cr, uid, {
+ 'type': 'auto',
+ 'line_id': [(6, 0, line.debit_move_line_id.id)],
+ }, context=context)
+ # mark the payment line for storno processed
+ if reconcile_id:
+ self.write(cr, uid, [payment_line_id],
+ {'storno': True}, context=context)
+ # put forth the invoice workflow
+ if line.move_line_id.invoice:
+ activity = (storno_retry and 'open_test'
+ or 'invoice_debit_denied')
+ netsvc.LocalService("workflow").trg_validate(
+ uid, 'account.invoice', line.move_line_id.invoice.id,
+ activity, cr)
+ return reconcile_id
+
+ def get_storno_account_id(self, cr, uid, payment_line_id, amount,
+ currency, context=None):
+ """
+ Check the match of the arguments, and return the account associated
+ with the storno.
+ Used in account_banking interactive mode
+
+ :param payment_line_id: the single payment line id
+ :param amount: the (signed) amount debited from the bank account
+ :param currency: the bank account's currency *browse object*
+ :return: an account if there is a full match, False otherwise
+ :rtype: database id of an account.account resource.
+ """
+
+ line = self.browse(cr, uid, payment_line_id)
+ account_id = False
+ if (line.debit_move_line_id and not line.storno and
+ self.pool.get('res.currency').is_zero(
+ cr, uid, currency, (
+ (line.debit_move_line_id.credit or 0.0) -
+ (line.debit_move_line_id.debit or 0.0) + amount))):
+ account_id = line.debit_move_line_id.account_id.id
+ return account_id
+
+ def debit_reconcile(self, cr, uid, payment_line_id, context=None):
+ """
+ Reconcile a debit order's payment line with the the move line
+ that it is based on. Called from payment_order.action_sent().
+ As the amount is derived directly from the counterpart move line,
+ we do not expect a write off. Take partially reconcilions into
+ account though.
+
+ :param payment_line_id: the single id of the canceled payment line
+ """
+
+ if isinstance(payment_line_id, (list, tuple)):
+ payment_line_id = payment_line_id[0]
+ reconcile_obj = self.pool.get('account.move.reconcile')
+ move_line_obj = self.pool.get('account.move.line')
+ payment_line = self.browse(cr, uid, payment_line_id, context=context)
+
+ debit_move_line = payment_line.debit_move_line_id
+ torec_move_line = payment_line.move_line_id
+
+ if payment_line.storno:
+ raise osv.except_osv(
+ _('Can not reconcile'),
+ _('Cancelation of payment line \'%s\' has already been ' +
+ 'processed') % payment_line.name)
+ if (not debit_move_line or not torec_move_line):
+ raise osv.except_osv(
+ _('Can not reconcile'),
+ _('No move line for line %s') % payment_line.name)
+ if torec_move_line.reconcile_id: # torec_move_line.reconcile_partial_id:
+ raise osv.except_osv(
+ _('Error'),
+ _('Move line %s has already been reconciled') %
+ torec_move_line.name
+ )
+ if debit_move_line.reconcile_id or debit_move_line.reconcile_partial_id:
+ raise osv.except_osv(
+ _('Error'),
+ _('Move line %s has already been reconciled') %
+ debit_move_line.name
+ )
+
+ def is_zero(total):
+ return self.pool.get('res.currency').is_zero(
+ cr, uid, debit_move_line.company_id.currency_id, total)
+
+ line_ids = [debit_move_line.id, torec_move_line.id]
+ if torec_move_line.reconcile_partial_id:
+ line_ids = [
+ x.id for x in debit_move_line.reconcile_partial_id.line_partial_ids] + [torec_move_line_id]
+
+ total = move_line_obj.get_balance(cr, uid, line_ids)
+ vals = {
+ 'type': 'auto',
+ 'line_id': is_zero(total) and [(6, 0, line_ids)] or [(6, 0, [])],
+ 'line_partial_ids': is_zero(total) and [(6, 0, [])] or [(6, 0, line_ids)],
+ }
+
+ if torec_move_line.reconcile_partial_id:
+ reconcile_obj.write(
+ cr, uid, debit_move_line.reconcile_partial_id.id,
+ vals, context=context)
+ else:
+ reconcile_obj.create(
+ cr, uid, vals, context=context)
+ for line_id in line_ids:
+ netsvc.LocalService("workflow").trg_trigger(
+ uid, 'account.move.line', line_id, cr)
+
+ # If a bank transaction of a storno was first confirmed
+ # and now canceled (the invoice is now in state 'debit_denied'
+ if torec_move_line.invoice:
+ netsvc.LocalService("workflow").trg_validate(
+ uid, 'account.invoice', torec_move_line.invoice.id,
+ 'undo_debit_denied', cr)
+
+
+
+ _columns = {
+ 'debit_move_line_id': fields.many2one(
+ # this line is part of the credit side of move 2a
+ # from the documentation
+ 'account.move.line', 'Debit move line',
+ readonly=True,
+ help="Move line through which the debit order pays the invoice"),
+ 'storno': fields.boolean(
+ 'Storno',
+ readonly=True,
+ help=("If this is true, the debit order has been canceled " +
+ "by the bank or by the customer")),
+ }
+payment_line()
+
+
+class payment_order_create(osv.osv_memory):
+ _inherit = 'payment.order.create'
+
+ def search_entries(self, cr, uid, ids, context=None):
+ """
+ This method taken from account_payment module.
+ We adapt the domain based on the payment_order_type
+ """
+ line_obj = self.pool.get('account.move.line')
+ mod_obj = self.pool.get('ir.model.data')
+ if context is None:
+ context = {}
+ data = self.read(cr, uid, ids, [], context=context)[0]
+ search_due_date = data['duedate']
+
+ ### start account_direct_debit ###
+ payment = self.pool.get('payment.order').browse(
+ cr, uid, context['active_id'], context=context)
+ # Search for move line to pay:
+ if payment.payment_order_type == 'debit':
+ domain = [
+ ('reconcile_id', '=', False),
+ ('account_id.type', '=', 'receivable'),
+ ('invoice_state', '!=', 'debit_denied'),
+ ('amount_to_receive', '>', 0),
+ ]
+ else:
+ domain = [
+ ('reconcile_id', '=', False),
+ ('account_id.type', '=', 'payable'),
+ ('amount_to_pay', '>', 0)
+ ]
+ # apply payment term filter
+ if payment.mode.payment_term_ids:
+ domain = domain + [
+ ('payment_term_id', 'in',
+ [term.id for term in payment.mode.payment_term_ids]
+ )
+ ]
+ # domain = [('reconcile_id', '=', False), ('account_id.type', '=', 'payable'), ('amount_to_pay', '>', 0)]
+ ### end account_direct_debit ###
+
+ domain = domain + ['|', ('date_maturity', '<=', search_due_date), ('date_maturity', '=', False)]
+ line_ids = line_obj.search(cr, uid, domain, context=context)
+ context.update({'line_ids': line_ids})
+ model_data_ids = mod_obj.search(cr, uid,[('model', '=', 'ir.ui.view'), ('name', '=', 'view_create_payment_order_lines')], context=context)
+ resource_id = mod_obj.read(cr, uid, model_data_ids, fields=['res_id'], context=context)[0]['res_id']
+ return {'name': ('Entry Lines'),
+ 'context': context,
+ 'view_type': 'form',
+ 'view_mode': 'form',
+ 'res_model': 'payment.order.create',
+ 'views': [(resource_id,'form')],
+ 'type': 'ir.actions.act_window',
+ 'target': 'new',
+ }
+payment_order_create()
+
+
+
=== added directory 'account_direct_debit/view'
=== added file 'account_direct_debit/view/account_invoice.xml'
--- account_direct_debit/view/account_invoice.xml 1970-01-01 00:00:00 +0000
+++ account_direct_debit/view/account_invoice.xml 2012-01-17 10:38:36 +0000
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+ <record id="invoice_form" model="ir.ui.view">
+ <field name="name">account.invoice.form</field>
+ <field name="model">account.invoice</field>
+ <field name="type">form</field>
+ <field name="inherit_id" ref="account.invoice_form"/>
+ <field name="arch" type="xml">
+ <data>
+ <!--
+ Add new state 'debit_denied' to applicable buttons.
+ Maybe apply trick in fields_view_get instead, for
+ better compatibility with other modules?
+ -->
+ <button name="invoice_open" position="attributes">
+ <attribute name="states">draft,proforma2,debit_denied</attribute>
+ </button>
+ <button string='Re-Open' position="attributes">
+ <attribute name="states">paid,debit_denied</attribute>
+ <!--
+ unintentional fix of
+ https://bugs.launchpad.net/openobject-addons/+bug/807543
+ -->
+ <attribute name="groups"/>
+ </button>
+ <button name="invoice_open" position="after">
+ <button name="invoice_debit_denied" states="paid"
+ string="Debit Denied" icon="gtk-cancel"/>
+ </button>
+ </data>
+ </field>
+ </record>
+ <record id="view_account_invoice_filter" model="ir.ui.view">
+ <field name="name">account.invoice.select direct debit</field>
+ <field name="model">account.invoice</field>
+ <field name="type">search</field>
+ <field name="inherit_id" ref="account.view_account_invoice_filter"/>
+ <field name="arch" type="xml">
+ <filter name="invoices" position="after">
+ <filter name="debit_denied" icon="terp-dolar_ok!" string="Debit denied" domain="[('state','=','debit_denied')]" help="Show only invoices with state Debit denied"/>
+ </filter>
+ </field>
+ </record>
+ </data>
+</openerp>
=== added file 'account_direct_debit/view/account_payment.xml'
--- account_direct_debit/view/account_payment.xml 1970-01-01 00:00:00 +0000
+++ account_direct_debit/view/account_payment.xml 2012-01-17 10:38:36 +0000
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+ <!-- distinguish between payment orders and debit orders in the menu -->
+ <record id="account_payment.action_payment_order_tree" model="ir.actions.act_window">
+ <field name="domain">[('payment_order_type', '=', 'payment')]</field>
+ <field name="context">{'search_payment_order_type': 'payment'}</field>
+ </record>
+
+ <record id="action_debit_order_tree" model="ir.actions.act_window">
+ <field name="name">Direct Debit Orders</field>
+ <field name="res_model">payment.order</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="context">{'search_payment_order_type': 'debit'}</field>
+ <field name="search_view_id" ref="account_payment.view_payment_order_search"/>
+ <field name="domain">[('payment_order_type', '=', 'debit')]</field>
+ <field name="help">A debit order is a debit request from your company to collect customer invoices. Here you can register all debit orders that should be done, keep track of all debit orders and mention the invoice reference and the partner the withdrawal should be done for.</field>
+ </record>
+
+ <menuitem action="action_debit_order_tree" id="menu_action_debit_order_form" parent="account_payment.menu_main_payment" sequence="4"/>
+
+ <record id="view_payment_order_form" model="ir.ui.view">
+ <field name="name">payment.order.form</field>
+ <field name="model">payment.order</field>
+ <field name="type">form</field>
+ <field name="inherit_id" ref="account_payment.view_payment_order_form"/>
+ <field name="priority" eval="60"/>
+ <field name="arch" type="xml">
+ <data>
+ <field name="reference" position="after">
+ <field name="payment_order_type"/>
+ </field>
+ <xpath expr="/form/group/button[@string='Select Invoices to Pay']"
+ position="attributes">
+ <attribute name="attrs">
+ {'invisible':['|',('state','!=','draft'),('payment_order_type', '!=', 'payment')]}
+ </attribute>
+ </xpath>
+ <xpath expr="/form/group/button[@string='Select Invoices to Pay']"
+ position="after">
+ <button colspan="2" name="%(account_payment.action_create_payment_order)s"
+ string="Select Invoices to Collect" type="action"
+ attrs="{'invisible':['|',('state','!=','draft'),('payment_order_type', '!=', 'debit')]}"
+ icon="gtk-find"
+ />
+ </xpath>
+ <xpath expr="//tree[@string='Payment Line']" position="inside">
+ <!-- the attrs do not work like this, apparently -->
+ <field name="storno" attrs="{'invisible': [(parent.payment_order_type, '!=', 'debit')]}"/>
+ </xpath>
+ </data>
+ </field>
+ </record>
+
+ <!-- Add transfer account for debit type modes -->
+ <record model="ir.ui.view" id="view_payment_mode_form">
+ <field name="name">payment.mode.form add transfer account</field>
+ <field name="model">payment.mode</field>
+ <field name="inherit_id" ref="account_banking.view_payment_mode_form_inherit"/>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <field name="type" position="after">
+ <field name="transfer_account_id"/>
+ <field name="transfer_journal_id"/>
+ <field name="payment_term_ids"/>
+ </field>
+ </field>
+ </record>
+
+ <record id="view_payment_line_tree" model="ir.ui.view">
+ <field name="name">Payment Lines</field>
+ <field name="model">payment.line</field>
+ <field name="type">tree</field>
+ <field name="inherit_id" ref="account_payment.view_payment_line_tree"/>
+ <field eval="4" name="priority"/>
+ <field name="arch" type="xml">
+ <field name="name" position="after">
+ <field name="storno"/>
+ </field>
+ </field>
+ </record>
+ </data>
+</openerp>
=== added directory 'account_direct_debit/workflow'
=== added file 'account_direct_debit/workflow/account_invoice.xml'
--- account_direct_debit/workflow/account_invoice.xml 1970-01-01 00:00:00 +0000
+++ account_direct_debit/workflow/account_invoice.xml 2012-01-17 10:38:36 +0000
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+ <record id="act_debit_denied" model="workflow.activity">
+ <field name="wkf_id" ref="account.wkf"/>
+ <field name="name">debit_denied</field>
+ <field name="action">action_debit_denied()</field>
+ <field name="kind">function</field>
+ </record>
+ <record id="paid_to_debit_denied" model="workflow.transition">
+ <field name="act_from" ref="account.act_paid"/>
+ <field name="act_to" ref="act_debit_denied"/>
+ <field name="signal">invoice_debit_denied</field>
+ </record>
+ <record id="debit_denied_to_paid" model="workflow.transition">
+ <field name="act_from" ref="act_debit_denied"/>
+ <field name="act_to" ref="account.act_paid"/>
+ <field name="condition">test_undo_debit_denied()</field>
+ <field name="signal">undo_debit_denied</field>
+ </record>
+ <record id="debit_denied_to_open" model="workflow.transition">
+ <field name="act_from" ref="act_debit_denied"/>
+ <field name="act_to" ref="account.act_open"/>
+ <field name="signal">invoice_open</field>
+ </record>
+ </data>
+</openerp>
=== added file 'account_direct_debit/workflow/account_payment.xml'
--- account_direct_debit/workflow/account_payment.xml 1970-01-01 00:00:00 +0000
+++ account_direct_debit/workflow/account_payment.xml 2012-01-17 10:38:36 +0000
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+ <!--
+ Transition to undo the payment order and reset to
+ sent, triggered by
+ cancelling a bank transaction with which the order
+ was reconciled.
+ For this, we need to cancel the flow stop on the done state,
+ unfortunately.
+ TODO: what is below is not enough. We need to inject
+ another state, 'sent_wait' between sent and done.
+ -->
+ <record id="account_payment.act_done" model="workflow.activity">
+ <field name="flow_stop">False</field>
+ </record>
+
+ <record id="trans_done_sent" model="workflow.transition">
+ <field name="act_from" ref="account_payment.act_done"/>
+ <field name="act_to" ref="account_banking.act_sent"/>
+ <field name="condition">test_undo_done()</field>
+ <field name="signal">undo_done</field>
+ </record>
+ </data>
+</openerp>
=== added directory 'account_payment_shortcut'
=== added file 'account_payment_shortcut/__init__.py'
--- account_payment_shortcut/__init__.py 1970-01-01 00:00:00 +0000
+++ account_payment_shortcut/__init__.py 2012-01-17 10:38:36 +0000
@@ -0,0 +1,1 @@
+import payment_order
=== added file 'account_payment_shortcut/__openerp__.py'
--- account_payment_shortcut/__openerp__.py 1970-01-01 00:00:00 +0000
+++ account_payment_shortcut/__openerp__.py 2012-01-17 10:38:36 +0000
@@ -0,0 +1,44 @@
+##############################################################################
+#
+# Copyright (C) 2011 Therp BV (<http://therp.nl>).
+# 2011 Smile BV (<http://smile.fr>).
+# 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 Payment Invoice Selection Shortcut',
+ 'version': '6.0.1.89',
+ 'license': 'GPL-3',
+ 'author': 'Smile / Therp BV',
+ 'website': 'https://launchpad.net/banking-addons',
+ 'category': 'Banking addons',
+ 'depends': ['account_payment'],
+ 'init_xml': [],
+ 'update_xml': [
+ ],
+ 'demo_xml': [],
+ 'description': '''
+When composing a payment order, select all candidates by default (in the second step of the "Select invoices to pay" wizard).
+ ''',
+ 'active': False,
+ 'installable': True,
+}
=== added file 'account_payment_shortcut/payment_order.py'
--- account_payment_shortcut/payment_order.py 1970-01-01 00:00:00 +0000
+++ account_payment_shortcut/payment_order.py 2012-01-17 10:38:36 +0000
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+from osv import osv, fields
+
+class payment_order_create(osv.osv_memory):
+
+ _inherit = 'payment.order.create'
+
+ def default_get(self, cr, uid, fields_list, context=None):
+ """
+ Automatically add the candidate move lines to
+ the payment order, instead of only applying them
+ to the domain.
+
+ We make use of the fact that the search_entries
+ method passes an action without a res_id so that a
+ new instance is created. Inject the line_ids, which have
+ been placed in the context at object
+ creation time.
+ """
+ res = super(payment_order_create, self).default_get(
+ cr, uid, fields_list, context=context)
+
+ if (fields_list and 'entries' in fields_list
+ and 'entries' not in res
+ and context and context.get('line_ids', False)
+ ):
+ res['entries'] = context['line_ids']
+
+ return res
+
+payment_order_create()