mirror of
https://github.com/OCA/bank-payment.git
synced 2025-02-02 10:37:31 +02:00
[IMP] split account_banking_payment from account_banking
We drop the bank statement related part and we manage the state of payment order with workflow transitions triggered by the reconciliation of moves lines on the transfer account.
This commit is contained in:
@@ -75,8 +75,6 @@ for electronic banking. It provides the following technical features:
|
||||
To enable the use of payment order to collect money for customers,
|
||||
it adds a payment_order_type (payment|debit) as a basis of direct debit support
|
||||
(this field becomes visible when account_direct_debit is installed).
|
||||
Refactoring note: this field should ideally go in account_direct_debit,
|
||||
but account_banking_payment currently depends on it.
|
||||
|
||||
Bug fixes and enhancement that should land in official addons:
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ class AccountMoveLine(orm.Model):
|
||||
# this file. -- Alexis de Lattre
|
||||
|
||||
def _amount_to_pay(self, cr, uid, ids, name, arg=None, context=None):
|
||||
""" Return the amount still to pay regarding all the payemnt orders
|
||||
""" Return the amount still to pay regarding all the payment orders
|
||||
(excepting cancelled orders)"""
|
||||
if not ids:
|
||||
return {}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<data>
|
||||
|
||||
<!--
|
||||
Add the payment mode type and transfer settings
|
||||
Add the payment mode type settings
|
||||
-->
|
||||
<record id="view_payment_mode_form_inherit" model="ir.ui.view">
|
||||
<field name="name">payment.mode.form.inherit</field>
|
||||
|
||||
@@ -1 +1 @@
|
||||
import model
|
||||
from . import model
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#
|
||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
||||
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
|
||||
# (C) 2014 ACSONE SA/NV (<http://acsone.eu>).
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
@@ -24,31 +25,28 @@
|
||||
##############################################################################
|
||||
|
||||
{
|
||||
'name': 'Account Banking - Payments',
|
||||
'version': '0.1.164',
|
||||
'name': 'Account Banking - Payments Transfer Account',
|
||||
'version': '0.2',
|
||||
'license': 'AGPL-3',
|
||||
'author': 'Banking addons community',
|
||||
'website': 'https://launchpad.net/banking-addons',
|
||||
'website': 'https://github.com/OCA/banking',
|
||||
'category': 'Banking addons',
|
||||
'depends': [
|
||||
'account_banking',
|
||||
'account_banking_payment_export',
|
||||
],
|
||||
],
|
||||
'data': [
|
||||
'view/account_payment.xml',
|
||||
'view/banking_transaction_wizard.xml',
|
||||
# TODO: 'view/account_payment.xml',
|
||||
'view/payment_mode.xml',
|
||||
'workflow/account_payment.xml',
|
||||
],
|
||||
'description': '''
|
||||
This addon adds payment reconciliation infrastructure to the Banking Addons.
|
||||
'description': '''Payment order reconciliation infrastructure
|
||||
|
||||
* Extends payments for digital banking:
|
||||
+ Adapted workflow in payments to reflect banking operations
|
||||
+ Relies on account_payment mechanics to extend with export generators.
|
||||
- ClieOp3 (NL) payment and direct debit orders files available as
|
||||
account_banking_nl_clieop
|
||||
This module reconciles invoices as soon as the payment order
|
||||
is sent, by creating a move to a transfer account (aka suspense account).
|
||||
When the moves on the suspense account are reconciled (typically through
|
||||
the bank statement reconciliation, the payment order moves to the done
|
||||
status).
|
||||
''',
|
||||
'auto_install': True,
|
||||
'installable': False,
|
||||
'auto_install': False,
|
||||
'installable': True,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import account_payment
|
||||
import payment_line
|
||||
import payment_mode
|
||||
import payment_order_create
|
||||
import banking_import_transaction
|
||||
import banking_transaction_wizard
|
||||
import banking_import_line
|
||||
from . import account_payment
|
||||
from . import payment_line
|
||||
from . import payment_mode
|
||||
from . import payment_order_create
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#
|
||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
||||
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
|
||||
# (C) 2014 ACSONE SA (<http://acsone.eu>).
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
@@ -25,7 +26,6 @@
|
||||
|
||||
from openerp.osv import orm, fields
|
||||
from openerp.tools.translate import _
|
||||
from openerp import netsvc
|
||||
|
||||
|
||||
class payment_order(orm.Model):
|
||||
@@ -124,103 +124,48 @@ class payment_order(orm.Model):
|
||||
])
|
||||
payment_line_obj.write(cr, uid, line_ids, kwargs)
|
||||
|
||||
def action_rejected(self, cr, uid, ids, *args):
|
||||
'''
|
||||
Set both self and payment lines to state 'rejected'.
|
||||
'''
|
||||
wf_service = netsvc.LocalService('workflow')
|
||||
for id in ids:
|
||||
wf_service.trg_validate(uid, 'payment.order', id, 'rejected', cr)
|
||||
def action_rejected(self, cr, uid, ids, context=None):
|
||||
return True
|
||||
|
||||
def set_done(self, cr, uid, ids, *args):
|
||||
'''
|
||||
Extend standard transition to update children as well.
|
||||
'''
|
||||
def action_done(self, cr, uid, ids, context=None):
|
||||
self._write_payment_lines(
|
||||
cr, uid, ids,
|
||||
date_done=fields.date.context_today(self, cr, uid))
|
||||
return super(payment_order, self).set_done(
|
||||
cr, uid, ids, *args
|
||||
)
|
||||
|
||||
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
|
||||
if not order.line_ids[0].transit_move_line_id:
|
||||
wf_service = netsvc.LocalService('workflow')
|
||||
wf_service.trg_validate(
|
||||
uid, 'payment.order', payment_order_id, 'done', cr)
|
||||
return False
|
||||
for order_line in order.line_ids:
|
||||
for line in order_line.transit_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.
|
||||
|
||||
Workflow appears to return False even on success so we just check
|
||||
the order's state that we know to be set to 'sent' in that case.
|
||||
"""
|
||||
self.pool.get('account.move.reconcile').unlink(
|
||||
cr, uid, [reconcile_id], context=context)
|
||||
netsvc.LocalService('workflow').trg_validate(
|
||||
uid, 'payment.order', payment_order_id, 'undo_done', cr)
|
||||
state = self.pool.get('payment.order').read(
|
||||
cr, uid, payment_order_id, ['state'], context=context)['state']
|
||||
if state != 'sent':
|
||||
raise orm.except_orm(
|
||||
_("Cannot unreconcile"),
|
||||
_("Cannot unreconcile payment order: "
|
||||
"Workflow will not allow it."))
|
||||
date_done=fields.date.context_today(self, cr, uid,
|
||||
context=context))
|
||||
self.write(cr, uid, ids,
|
||||
{'date_done': fields.date.
|
||||
context_today(self, cr, uid, context=context)})
|
||||
# state is written in workflow definition
|
||||
return True
|
||||
|
||||
def test_undo_done(self, cr, uid, ids, context=None):
|
||||
def _get_transfer_move_lines(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.
|
||||
|
||||
Test if the payment order has not been reconciled. Depends
|
||||
on the restriction that transit move lines should use an
|
||||
account of type 'other', and on the restriction of payment
|
||||
and debit orders that they only take moves on accounts
|
||||
payable/receivable.
|
||||
Get the transfer move lines (on the transfer account).
|
||||
"""
|
||||
res = []
|
||||
for order in self.browse(cr, uid, ids, context=context):
|
||||
for order_line in order.line_ids:
|
||||
if order_line.transit_move_line_id.move_id:
|
||||
for line in \
|
||||
order_line.transit_move_line_id.move_id.line_id:
|
||||
if (line.account_id.type == 'other' and
|
||||
line.reconcile_id):
|
||||
return False
|
||||
return True
|
||||
move_line = order_line.transfer_move_line_id
|
||||
if move_line:
|
||||
res.append(move_line)
|
||||
return res
|
||||
|
||||
def get_transfer_move_line_ids(self, cr, uid, ids, context=None):
|
||||
return [move_line.id for move_line in
|
||||
self._get_transfer_move_lines(cr, uid, ids, context=context)]
|
||||
|
||||
def test_done(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
Test if all moves on the transfer account are reconciled.
|
||||
|
||||
Called from the workflow to move to the done state when
|
||||
all transfer move have been reconciled through bank statements.
|
||||
"""
|
||||
return all([move_line.reconcile_id for move_line in
|
||||
self._get_transfer_move_lines(cr, uid, ids, context)])
|
||||
|
||||
def test_undo_done(self, cr, uid, ids, context=None):
|
||||
return not self.test_done(cr, uid, ids, context=context)
|
||||
|
||||
def _prepare_transfer_move(
|
||||
self, cr, uid, order, line, labels, context=None):
|
||||
@@ -247,7 +192,7 @@ class payment_order(orm.Model):
|
||||
or line.move_line_id.name)
|
||||
or line.communication),
|
||||
'move_id': move_id,
|
||||
'partner_id': False,
|
||||
'partner_id': line.partner_id.id,
|
||||
'account_id': order.mode.transfer_account_id.id,
|
||||
'credit': (order.payment_order_type == 'payment'
|
||||
and line.amount or 0.0),
|
||||
@@ -311,19 +256,20 @@ class payment_order(orm.Model):
|
||||
|
||||
# TODO: take multicurrency into account
|
||||
|
||||
# create the debit move line on the transfer account
|
||||
# create the payment/debit move line on the transfer account
|
||||
ml_vals = self._prepare_move_line_transfer_account(
|
||||
cr, uid, order, line, move_id, labels, context=context)
|
||||
account_move_line_obj.create(cr, uid, ml_vals, context=context)
|
||||
|
||||
# create the debit move line on the partner account
|
||||
# create the payment/debit counterpart move line
|
||||
# on the partner account
|
||||
self._update_move_line_partner_account(
|
||||
cr, uid, order, line, ml_vals, context=context)
|
||||
reconcile_move_line_id = account_move_line_obj.create(
|
||||
cr, uid, ml_vals, context=context)
|
||||
|
||||
# register the debit move line on the payment line
|
||||
# and call reconciliation on it
|
||||
# register the payment/debit move line
|
||||
# on the payment line and call reconciliation on it
|
||||
payment_line_obj.write(
|
||||
cr, uid, line.id,
|
||||
{'transit_move_line_id': reconcile_move_line_id},
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
||||
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
# 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 openerp.osv import orm, fields
|
||||
|
||||
|
||||
class banking_import_line(orm.TransientModel):
|
||||
_inherit = 'banking.import.line'
|
||||
_columns = {
|
||||
'payment_order_id': fields.many2one(
|
||||
'payment.order', 'Payment order'),
|
||||
'transaction_type': fields.selection([
|
||||
# Add payment order related transaction types
|
||||
('invoice', 'Invoice payment'),
|
||||
('payment_order_line', 'Payment from a payment order'),
|
||||
('payment_order', 'Aggregate payment order'),
|
||||
('storno', 'Canceled debit order'),
|
||||
('bank_costs', 'Bank costs'),
|
||||
('unknown', 'Unknown'),
|
||||
], 'Transaction type'),
|
||||
}
|
||||
@@ -1,410 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
||||
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
# 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 openerp.osv import orm, fields
|
||||
from openerp import netsvc
|
||||
from openerp.tools.translate import _
|
||||
from openerp.addons.decimal_precision import decimal_precision as dp
|
||||
from openerp.addons.account_banking.parsers.models import (
|
||||
mem_bank_transaction as bt
|
||||
)
|
||||
|
||||
|
||||
class banking_import_transaction(orm.Model):
|
||||
_inherit = 'banking.import.transaction'
|
||||
|
||||
def _match_payment_order(
|
||||
self, cr, uid, trans, log, order_type='payment', context=None):
|
||||
|
||||
def equals_order_amount(payment_order, transferred_amount):
|
||||
if (not hasattr(payment_order, 'payment_order_type')
|
||||
or payment_order.payment_order_type == 'payment'):
|
||||
sign = 1
|
||||
else:
|
||||
sign = -1
|
||||
total = payment_order.total + sign * transferred_amount
|
||||
return self.pool.get('res.currency').is_zero(
|
||||
cr, uid, trans.statement_line_id.statement_id.currency, total)
|
||||
|
||||
payment_order_obj = self.pool.get('payment.order')
|
||||
|
||||
order_ids = payment_order_obj.search(
|
||||
cr, uid, [('payment_order_type', '=', order_type),
|
||||
('state', '=', 'sent'),
|
||||
('date_sent', '<=', trans.execution_date),
|
||||
],
|
||||
limit=0, context=context)
|
||||
orders = payment_order_obj.browse(cr, uid, order_ids, context)
|
||||
candidates = [x for x in orders if
|
||||
equals_order_amount(x, trans.statement_line_id.amount)]
|
||||
if len(candidates) > 0:
|
||||
# retrieve the common account_id, if any
|
||||
account_id = False
|
||||
transit_move_lines = candidates[0].line_ids[0].transit_move_line_id
|
||||
if transit_move_lines:
|
||||
for line in transit_move_lines.move_id.line_id:
|
||||
if line.account_id.type == 'other':
|
||||
account_id = line.account_id.id
|
||||
break
|
||||
return dict(
|
||||
move_line_ids=False,
|
||||
match_type='payment_order',
|
||||
payment_order_ids=[x.id for x in candidates],
|
||||
account_id=account_id,
|
||||
partner_id=False,
|
||||
partner_bank_id=False,
|
||||
reference=False,
|
||||
type='general',
|
||||
)
|
||||
return False
|
||||
|
||||
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.statement_line_id.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.
|
||||
|
||||
def bank_match(account, partner_bank):
|
||||
"""
|
||||
Returns whether a given account number is equivalent to a
|
||||
partner bank in the database. We simply call the search method,
|
||||
which checks bank account number, disregarding spaces.
|
||||
|
||||
:param account: string representation of a bank account number
|
||||
:param partner_bank: browse record of model res.partner.bank
|
||||
"""
|
||||
return partner_bank.id in self.pool['res.partner.bank'].search(
|
||||
cr, uid, [('acc_number', '=', account)])
|
||||
|
||||
digits = dp.get_precision('Account')(cr)[1]
|
||||
candidates = [
|
||||
line for line in payment_lines
|
||||
if (line.communication == trans.reference
|
||||
and round(line.amount, digits) == -round(
|
||||
trans.statement_line_id.amount, digits)
|
||||
and bank_match(trans.remote_account, line.bank_id))
|
||||
]
|
||||
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
|
||||
|
||||
def _confirm_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_pool = self.pool.get('payment.line')
|
||||
statement_line_pool = self.pool.get('account.bank.statement.line')
|
||||
transaction = self.browse(cr, uid, transaction_id, context=context)
|
||||
if not transaction.payment_line_id:
|
||||
raise orm.except_orm(
|
||||
_("Cannot link with storno"),
|
||||
_("No direct debit order item"))
|
||||
reconcile_id = payment_line_pool.debit_storno(
|
||||
cr, uid,
|
||||
transaction.payment_line_id.id,
|
||||
transaction.statement_line_id.amount,
|
||||
transaction.statement_line_id.currency,
|
||||
transaction.storno_retry,
|
||||
context=context)
|
||||
statement_line_pool.write(
|
||||
cr, uid, transaction.statement_line_id.id,
|
||||
{'reconcile_id': reconcile_id}, context=context)
|
||||
transaction.refresh()
|
||||
|
||||
def _confirm_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')
|
||||
statement_line_pool = self.pool.get('account.bank.statement.line')
|
||||
transaction = self.browse(cr, uid, transaction_id, context=context)
|
||||
if not transaction.payment_order_id:
|
||||
raise orm.except_orm(
|
||||
_("Cannot reconcile"),
|
||||
_("Cannot reconcile: no direct debit order"))
|
||||
reconcile_id = 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)
|
||||
statement_line_pool.write(
|
||||
cr, uid, transaction.statement_line_id.id,
|
||||
{'reconcile_id': reconcile_id}, context=context)
|
||||
|
||||
def _confirm_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, {
|
||||
'date_done': transaction.statement_line_id.date,
|
||||
}
|
||||
)
|
||||
self._confirm_move(cr, uid, transaction_id, context=context)
|
||||
# Check if the payment order is 'done'
|
||||
order_id = transaction.payment_line_id.order_id.id
|
||||
other_lines = payment_line_obj.search(
|
||||
cr, uid, [
|
||||
('order_id', '=', order_id),
|
||||
('date_done', '=', False),
|
||||
], context=context)
|
||||
if not other_lines:
|
||||
wf_service = netsvc.LocalService('workflow')
|
||||
wf_service.trg_validate(
|
||||
uid, 'payment.order', order_id, 'done', cr)
|
||||
|
||||
def _cancel_payment(
|
||||
self, cr, uid, transaction_id, context=None):
|
||||
"""
|
||||
Do not support cancelling individual lines yet, because the workflow
|
||||
of the payment order does not support reopening.
|
||||
"""
|
||||
raise orm.except_orm(
|
||||
_("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 orm.except_orm(
|
||||
_("Cannot unreconcile"),
|
||||
_("Cannot unreconcile: no payment or direct debit order"))
|
||||
if not transaction.statement_line_id.reconcile_id:
|
||||
raise orm.except_orm(
|
||||
_("Cannot unreconcile"),
|
||||
_("Payment orders without transfer move lines cannot be "
|
||||
"unreconciled this way"))
|
||||
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_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 orm.except_orm(
|
||||
_("Cannot cancel link with storno"),
|
||||
_("No direct debit order item"))
|
||||
if not transaction.payment_line_id.storno:
|
||||
raise orm.except_orm(
|
||||
_("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
|
||||
move_lines = []
|
||||
for move in transaction.statement_line_id.move_ids:
|
||||
# There should usually be just one move, I think
|
||||
move_lines += move.line_id
|
||||
for line in move_lines:
|
||||
if line.account_id.id != account_id:
|
||||
cancel_line = line
|
||||
break
|
||||
if not cancel_line:
|
||||
raise orm.except_orm(
|
||||
_("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)
|
||||
|
||||
_columns = {
|
||||
'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'),
|
||||
'payment_line_id': fields.many2one('payment.line', 'Payment line'),
|
||||
}
|
||||
|
||||
def _get_match_multi(self, cr, uid, ids, name, args, context=None):
|
||||
if not ids:
|
||||
return {}
|
||||
res = super(banking_import_transaction, self)._get_match_multi(
|
||||
cr, uid, ids, name, args, context=context)
|
||||
for transaction in self.browse(cr, uid, ids, context):
|
||||
if transaction.match_type == 'payment_order':
|
||||
if (transaction.payment_order_ids and not
|
||||
transaction.payment_order_id):
|
||||
res[transaction.id] = True
|
||||
return res
|
||||
|
||||
def clear_and_write(self, cr, uid, ids, vals=None, context=None):
|
||||
write_vals = {
|
||||
'payment_line_id': False,
|
||||
'payment_order_id': False,
|
||||
'payment_order_ids': [(6, 0, [])],
|
||||
}
|
||||
write_vals.update(vals or {})
|
||||
return super(banking_import_transaction, self).clear_and_write(
|
||||
cr, uid, ids, vals=vals, context=context)
|
||||
|
||||
def move_info2values(self, move_info):
|
||||
vals = super(banking_import_transaction, self).move_info2values(
|
||||
move_info)
|
||||
vals['payment_line_id'] = move_info.get('payment_line_id', False)
|
||||
vals['payment_order_ids'] = [
|
||||
(6, 0, move_info.get('payment_order_ids') or [])]
|
||||
vals['payment_order_id'] = (
|
||||
move_info.get('payment_order_ids', False) and
|
||||
len(move_info['payment_order_ids']) == 1 and
|
||||
move_info['payment_order_ids'][0]
|
||||
)
|
||||
return vals
|
||||
|
||||
def hook_match_payment(self, cr, uid, transaction, log, context=None):
|
||||
"""
|
||||
Called from match() in the core module.
|
||||
Match payment batches, direct debit orders and stornos
|
||||
"""
|
||||
move_info = False
|
||||
if transaction.type == bt.PAYMENT_BATCH:
|
||||
move_info = self._match_payment_order(
|
||||
cr, uid, transaction, log,
|
||||
order_type='payment', context=context)
|
||||
elif transaction.type == bt.DIRECT_DEBIT:
|
||||
move_info = self._match_payment_order(
|
||||
cr, uid, transaction, log,
|
||||
order_type='debit', context=context)
|
||||
elif transaction.type == bt.STORNO:
|
||||
move_info = self._match_storno(
|
||||
cr, uid, transaction, log,
|
||||
context=context)
|
||||
return move_info
|
||||
|
||||
def __init__(self, pool, cr):
|
||||
"""
|
||||
Updating the function maps to handle the match types that this
|
||||
module adds.
|
||||
"""
|
||||
super(banking_import_transaction, self).__init__(pool, cr)
|
||||
|
||||
self.confirm_map.update({
|
||||
'storno': banking_import_transaction._confirm_storno,
|
||||
'payment_order': banking_import_transaction._confirm_payment_order,
|
||||
'payment': banking_import_transaction._confirm_payment,
|
||||
'payment_order_manual': (
|
||||
banking_import_transaction._confirm_payment_order),
|
||||
'payment_manual': banking_import_transaction._confirm_payment,
|
||||
})
|
||||
|
||||
self.cancel_map.update({
|
||||
'storno': banking_import_transaction._cancel_storno,
|
||||
'payment_order': banking_import_transaction._cancel_payment_order,
|
||||
'payment': banking_import_transaction._cancel_payment,
|
||||
'payment_order_manual': (
|
||||
banking_import_transaction._cancel_payment_order),
|
||||
'payment_manual': banking_import_transaction._cancel_payment,
|
||||
})
|
||||
@@ -1,122 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
||||
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
|
||||
#
|
||||
# All other contributions are (C) by their respective contributors
|
||||
#
|
||||
# All Rights Reserved
|
||||
#
|
||||
# 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 openerp.osv import orm, fields
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
||||
class banking_transaction_wizard(orm.TransientModel):
|
||||
_inherit = 'banking.transaction.wizard'
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
"""
|
||||
Check for manual payment orders or lines
|
||||
"""
|
||||
if not vals or not ids:
|
||||
return True
|
||||
manual_payment_order_id = vals.pop('manual_payment_order_id', False)
|
||||
manual_payment_line_id = vals.pop('manual_payment_line_id', False)
|
||||
res = super(banking_transaction_wizard, self).write(
|
||||
cr, uid, ids, vals, context=context)
|
||||
if manual_payment_order_id or manual_payment_line_id:
|
||||
transaction_id = self.browse(
|
||||
cr, uid, ids[0],
|
||||
context=context).import_transaction_id
|
||||
write_vals = {}
|
||||
if manual_payment_order_id:
|
||||
payment_order = self.pool.get('payment.order').browse(
|
||||
cr, uid, manual_payment_order_id,
|
||||
context=context)
|
||||
if payment_order.payment_order_type == 'payment':
|
||||
sign = 1
|
||||
else:
|
||||
sign = -1
|
||||
total = (payment_order.total + sign *
|
||||
transaction_id.statement_line_id.amount)
|
||||
if not self.pool.get('res.currency').is_zero(
|
||||
cr, uid,
|
||||
transaction_id.statement_line_id.statement_id.currency,
|
||||
total):
|
||||
raise orm.except_orm(
|
||||
_('Error'),
|
||||
_('When matching a payment order, the amounts have to '
|
||||
'match exactly'))
|
||||
|
||||
if (payment_order.mode
|
||||
and payment_order.mode.transfer_account_id):
|
||||
transaction_id.statement_line_id.write({
|
||||
'account_id': (
|
||||
payment_order.mode.transfer_account_id.id),
|
||||
})
|
||||
write_vals.update(
|
||||
{'payment_order_id': manual_payment_order_id,
|
||||
'match_type': 'payment_order_manual'})
|
||||
else:
|
||||
write_vals.update(
|
||||
{'payment_line_id': manual_payment_line_id,
|
||||
'match_type': 'payment_manual'})
|
||||
self.pool.get('banking.import.transaction').clear_and_write(
|
||||
cr, uid, transaction_id.id, write_vals, context=context)
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'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',
|
||||
),
|
||||
'manual_payment_order_id': fields.many2one(
|
||||
'payment.order',
|
||||
'Match this payment order',
|
||||
domain=[
|
||||
('state', '=', 'sent'),
|
||||
],
|
||||
),
|
||||
'manual_payment_line_id': fields.many2one(
|
||||
'payment.line',
|
||||
'Match this payment line',
|
||||
domain=[
|
||||
('order_id.state', '=', 'sent'),
|
||||
('date_done', '=', False),
|
||||
],
|
||||
),
|
||||
}
|
||||
@@ -24,7 +24,7 @@
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import orm, fields
|
||||
from openerp import netsvc
|
||||
from openerp import workflow
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
||||
@@ -36,16 +36,36 @@ class payment_line(orm.Model):
|
||||
accounts.
|
||||
'''
|
||||
_inherit = 'payment.line'
|
||||
|
||||
def _get_transfer_move_line(self, cr, uid, ids, name, arg, context=None):
|
||||
res = {}
|
||||
for order_line in self.browse(cr, uid, ids, context=context):
|
||||
if not order_line.transit_move_line_id:
|
||||
continue
|
||||
if len(order_line.transit_move_line_id.move_id.line_id) != 2:
|
||||
continue
|
||||
for move_line in order_line.transit_move_line_id.move_id.line_id:
|
||||
if move_line.id != order_line.transit_move_line_id.id:
|
||||
res[order_line.id] = move_line.id
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'msg': fields.char('Message', size=255, required=False, readonly=True),
|
||||
'date_done': fields.date(
|
||||
'Date Confirmed', select=True, readonly=True),
|
||||
'transit_move_line_id': fields.many2one(
|
||||
# this line is part of the credit side of move 2a
|
||||
# from the documentation
|
||||
'account.move.line', 'Debit move line',
|
||||
'account.move.line', 'Transfer move line',
|
||||
readonly=True,
|
||||
help="Move line through which the debit order pays the invoice",
|
||||
help="Move line through which the payment/debit order "
|
||||
"pays the invoice",
|
||||
),
|
||||
'transfer_move_line_id': fields.function(
|
||||
_get_transfer_move_line,
|
||||
type='many2one',
|
||||
relation='account.move.line',
|
||||
string='Transfer move line counterpart',
|
||||
read_only=True,
|
||||
help="Counterpart move line on the transfer account",
|
||||
),
|
||||
}
|
||||
|
||||
@@ -98,7 +118,7 @@ class payment_line(orm.Model):
|
||||
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
|
||||
we do not expect a write off. Take partial reconciliations into
|
||||
account though.
|
||||
|
||||
:param payment_line_id: the single id of the canceled payment line
|
||||
@@ -160,12 +180,12 @@ class payment_line(orm.Model):
|
||||
reconcile_obj.create(
|
||||
cr, uid, vals, context=context)
|
||||
for line_id in line_ids:
|
||||
netsvc.LocalService("workflow").trg_trigger(
|
||||
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(
|
||||
workflow.trg_validate(
|
||||
uid, 'account.invoice', torec_move_line.invoice.id,
|
||||
'undo_debit_denied', cr)
|
||||
|
||||
@@ -43,6 +43,7 @@ class payment_mode(orm.Model):
|
||||
help=('Journal to write payment entries when confirming '
|
||||
'a debit order of this mode'),
|
||||
),
|
||||
# TODO: extract this to account_banking_payment_term
|
||||
'payment_term_ids': fields.many2many(
|
||||
'account.payment.term', 'account_payment_order_terms_rel',
|
||||
'mode_id', 'term_id', 'Payment terms',
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
from openerp.osv import orm
|
||||
|
||||
|
||||
# TODO: extract this in anoter module such as account_banking_payment_term
|
||||
|
||||
|
||||
class payment_order_create(orm.TransientModel):
|
||||
_inherit = 'payment.order.create'
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="transaction_wizard">
|
||||
<field name="name">transaction.wizard</field>
|
||||
<field name="model">banking.transaction.wizard</field>
|
||||
<field name="inherit_id"
|
||||
ref="account_banking.transaction_wizard_first" />
|
||||
<field name="arch" type="xml">
|
||||
<field name="invoice_ids" position="before">
|
||||
<field name="payment_order_ids" invisible="True"/>
|
||||
</field>
|
||||
<xpath expr="//group/separator[@string='Multiple matches']/.."
|
||||
position="after">
|
||||
<group>
|
||||
<field name='payment_line_id'
|
||||
attrs="{'invisible': [('match_type', 'not in',
|
||||
('storno', 'payment', 'payment_manual'))]}" />
|
||||
</group>
|
||||
</xpath>
|
||||
<field name="move_line_id" position="after">
|
||||
<group>
|
||||
<field name='payment_order_id'
|
||||
attrs="{'readonly': [('match_multi', '=', False)],
|
||||
'invisible': [('match_type', 'not in',
|
||||
('payment_order', 'payment_order_manual'))]}"
|
||||
domain="[('id', 'in', payment_order_ids[0][2])]" />
|
||||
</group>
|
||||
</field>
|
||||
<page string="Manual match" position="after">
|
||||
<page string="Match Payments">
|
||||
<separator string="Match this payment line" colspan="4"/>
|
||||
<field name="manual_payment_line_id"/>
|
||||
<separator string="Match this payment order" colspan="4"/>
|
||||
<field name="manual_payment_order_id"/>
|
||||
</page>
|
||||
</page>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
/>
|
||||
</group>
|
||||
<group colspan="2">
|
||||
<!-- TODO: extract to account_banking_payment_term -->
|
||||
<separator colspan="2"
|
||||
string="Optional filter by payment term" />
|
||||
<field name="payment_term_ids" nolabel="1" colspan="2"/>
|
||||
|
||||
@@ -2,21 +2,21 @@
|
||||
<openerp>
|
||||
<data>
|
||||
<!-- New activity for workflow payment order: sent -->
|
||||
<record id="account_banking.act_sent" model="workflow.activity">
|
||||
<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()</field>
|
||||
<field name="kind">function</field>
|
||||
</record>
|
||||
<!-- New activity for workflow payment order: sent -->
|
||||
<record id="account_banking.act_sent_wait" model="workflow.activity">
|
||||
<record id="act_sent_wait" model="workflow.activity">
|
||||
<field name="name">sent_wait</field>
|
||||
<field name="wkf_id" ref="account_payment.wkf_payment_order"/>
|
||||
<field name="action">write({'state': 'sent'})</field>
|
||||
<field name="kind">function</field>
|
||||
</record>
|
||||
<!-- New activity for workflow payment order: rejected -->
|
||||
<record id="account_banking.act_rejected" model="workflow.activity">
|
||||
<record id="act_rejected" model="workflow.activity">
|
||||
<field name="name">rejected</field>
|
||||
<field name="wkf_id" ref="account_payment.wkf_payment_order"/>
|
||||
<field name="action">action_rejected()
|
||||
@@ -26,30 +26,38 @@ write({'state':'rejected'})</field>
|
||||
<!-- Rewrite existing open -> done transition to include 'sent' stage -->
|
||||
<record id="account_payment.trans_open_done" model="workflow.transition">
|
||||
<field name="act_from" ref="account_payment.act_open"/>
|
||||
<field name="act_to" ref="account_banking.act_sent"/>
|
||||
<field name="act_to" ref="act_sent"/>
|
||||
<field name="signal">sent</field>
|
||||
</record>
|
||||
<!-- the done signal continues to work but goes to sent -->
|
||||
<record id="account_banking.trans_open_done" model="workflow.transition">
|
||||
<record id="trans_open_done" model="workflow.transition">
|
||||
<field name="act_from" ref="account_payment.act_open"/>
|
||||
<field name="act_to" ref="account_banking.act_sent"/>
|
||||
<field name="act_to" ref="act_sent"/>
|
||||
<field name="signal">done</field>
|
||||
</record>
|
||||
<!-- From sent straight to sent_wait -->
|
||||
<record id="account_banking.trans_sent_sent_wait" model="workflow.transition">
|
||||
<field name="act_from" ref="account_banking.act_sent"/>
|
||||
<field name="act_to" ref="account_banking.act_sent_wait"/>
|
||||
<record id="trans_sent_sent_wait" model="workflow.transition">
|
||||
<field name="act_from" ref="act_sent"/>
|
||||
<field name="act_to" ref="act_sent_wait"/>
|
||||
</record>
|
||||
<!-- Reconciliation from the banking statement leads to done state -->
|
||||
<record id="account_banking.trans_sent_done" model="workflow.transition">
|
||||
<field name="act_from" ref="account_banking.act_sent_wait"/>
|
||||
<record id="trans_sent_done" model="workflow.transition">
|
||||
<field name="act_from" ref="act_sent_wait"/>
|
||||
<field name="act_to" ref="account_payment.act_done"/>
|
||||
<field name="condition">test_done()</field>
|
||||
<field name="signal">done</field>
|
||||
</record>
|
||||
<record id="trans_sent_done_auto" model="workflow.transition">
|
||||
<field name="act_from" ref="act_sent_wait"/>
|
||||
<field name="act_to" ref="account_payment.act_done"/>
|
||||
<field name="condition">test_done()</field>
|
||||
<field name="trigger_model">account.move.line</field>
|
||||
<field name="trigger_expr_id">get_transfer_move_line_ids()</field>
|
||||
</record>
|
||||
<!-- Rejected by the bank -->
|
||||
<record id="account_banking.trans_sent_rejected" model="workflow.transition">
|
||||
<field name="act_from" ref="account_banking.act_sent"/>
|
||||
<field name="act_to" ref="account_banking.act_rejected"/>
|
||||
<record id="trans_sent_rejected" model="workflow.transition">
|
||||
<field name="act_from" ref="act_sent"/>
|
||||
<field name="act_to" ref="act_rejected"/>
|
||||
<field name="signal">rejected</field>
|
||||
</record>
|
||||
<!--
|
||||
@@ -60,16 +68,25 @@ write({'state':'rejected'})</field>
|
||||
unfortunately.
|
||||
-->
|
||||
<record id="account_payment.act_done" model="workflow.activity">
|
||||
<field name="action">action_done()
|
||||
write({'state':'done'})</field>
|
||||
<field name="flow_stop" eval="False"/>
|
||||
</record>
|
||||
|
||||
<!-- Cancel the reconciled payment order -->
|
||||
<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_wait"/>
|
||||
<field name="act_to" ref="act_sent_wait"/>
|
||||
<field name="condition">test_undo_done()</field>
|
||||
<field name="signal">undo_done</field>
|
||||
</record>
|
||||
<record id="trans_done_sent_auto" model="workflow.transition">
|
||||
<field name="act_from" ref="account_payment.act_done"/>
|
||||
<field name="act_to" ref="act_sent_wait"/>
|
||||
<field name="condition">test_undo_done()</field>
|
||||
<field name="trigger_model">account.move.line</field>
|
||||
<field name="trigger_expr_id">get_transfer_move_line_ids()</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
Reference in New Issue
Block a user