From 424c4067cb33dfc4c6903cef52f4ea9324580ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Tue, 16 Sep 2014 18:52:28 +0200 Subject: [PATCH] [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. --- account_banking_payment_export/__openerp__.py | 2 - .../models/account_move_line.py | 2 +- .../views/payment_mode.xml | 2 +- account_banking_payment_transfer/__init__.py | 2 +- .../__openerp__.py | 30 +- .../model/__init__.py | 11 +- .../model/account_payment.py | 132 ++---- .../model/banking_import_line.py | 43 -- .../model/banking_import_transaction.py | 410 ------------------ .../model/banking_transaction_wizard.py | 122 ------ .../model/payment_line.py | 36 +- .../model/payment_mode.py | 1 + .../model/payment_order_create.py | 3 + .../view/banking_transaction_wizard.xml | 43 -- .../view/payment_mode.xml | 1 + .../workflow/account_payment.xml | 47 +- 16 files changed, 125 insertions(+), 762 deletions(-) delete mode 100644 account_banking_payment_transfer/model/banking_import_line.py delete mode 100644 account_banking_payment_transfer/model/banking_import_transaction.py delete mode 100644 account_banking_payment_transfer/model/banking_transaction_wizard.py delete mode 100644 account_banking_payment_transfer/view/banking_transaction_wizard.xml diff --git a/account_banking_payment_export/__openerp__.py b/account_banking_payment_export/__openerp__.py index 8b8369136..9ce6d502d 100644 --- a/account_banking_payment_export/__openerp__.py +++ b/account_banking_payment_export/__openerp__.py @@ -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: diff --git a/account_banking_payment_export/models/account_move_line.py b/account_banking_payment_export/models/account_move_line.py index 09b4f6140..d8ca2fd1f 100644 --- a/account_banking_payment_export/models/account_move_line.py +++ b/account_banking_payment_export/models/account_move_line.py @@ -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 {} diff --git a/account_banking_payment_export/views/payment_mode.xml b/account_banking_payment_export/views/payment_mode.xml index 273317bc5..31e5bce0c 100644 --- a/account_banking_payment_export/views/payment_mode.xml +++ b/account_banking_payment_export/views/payment_mode.xml @@ -3,7 +3,7 @@ payment.mode.form.inherit diff --git a/account_banking_payment_transfer/__init__.py b/account_banking_payment_transfer/__init__.py index 16e8b082f..9186ee3ad 100644 --- a/account_banking_payment_transfer/__init__.py +++ b/account_banking_payment_transfer/__init__.py @@ -1 +1 @@ -import model +from . import model diff --git a/account_banking_payment_transfer/__openerp__.py b/account_banking_payment_transfer/__openerp__.py index cd7621e7a..8ddfdbe0a 100644 --- a/account_banking_payment_transfer/__openerp__.py +++ b/account_banking_payment_transfer/__openerp__.py @@ -3,6 +3,7 @@ # # Copyright (C) 2009 EduSense BV (). # (C) 2011 - 2013 Therp BV (). +# (C) 2014 ACSONE SA/NV (). # # 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, } diff --git a/account_banking_payment_transfer/model/__init__.py b/account_banking_payment_transfer/model/__init__.py index 60600f542..9069347e8 100644 --- a/account_banking_payment_transfer/model/__init__.py +++ b/account_banking_payment_transfer/model/__init__.py @@ -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 diff --git a/account_banking_payment_transfer/model/account_payment.py b/account_banking_payment_transfer/model/account_payment.py index 1e2e31277..4a56cfa1b 100644 --- a/account_banking_payment_transfer/model/account_payment.py +++ b/account_banking_payment_transfer/model/account_payment.py @@ -3,6 +3,7 @@ # # Copyright (C) 2009 EduSense BV (). # (C) 2011 - 2013 Therp BV (). +# (C) 2014 ACSONE SA (). # # 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}, diff --git a/account_banking_payment_transfer/model/banking_import_line.py b/account_banking_payment_transfer/model/banking_import_line.py deleted file mode 100644 index da63b136a..000000000 --- a/account_banking_payment_transfer/model/banking_import_line.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Copyright (C) 2009 EduSense BV (). -# (C) 2011 - 2013 Therp BV (). -# -# 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 . -# -############################################################################## - -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'), - } diff --git a/account_banking_payment_transfer/model/banking_import_transaction.py b/account_banking_payment_transfer/model/banking_import_transaction.py deleted file mode 100644 index 48edd7979..000000000 --- a/account_banking_payment_transfer/model/banking_import_transaction.py +++ /dev/null @@ -1,410 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Copyright (C) 2009 EduSense BV (). -# (C) 2011 - 2013 Therp BV (). -# -# 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 . -# -############################################################################## - -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, - }) diff --git a/account_banking_payment_transfer/model/banking_transaction_wizard.py b/account_banking_payment_transfer/model/banking_transaction_wizard.py deleted file mode 100644 index dcc4d781b..000000000 --- a/account_banking_payment_transfer/model/banking_transaction_wizard.py +++ /dev/null @@ -1,122 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Copyright (C) 2009 EduSense BV (). -# (C) 2011 - 2013 Therp BV (). -# -# 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 . -# -############################################################################## - -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), - ], - ), - } diff --git a/account_banking_payment_transfer/model/payment_line.py b/account_banking_payment_transfer/model/payment_line.py index 0d41a966e..03bc427aa 100644 --- a/account_banking_payment_transfer/model/payment_line.py +++ b/account_banking_payment_transfer/model/payment_line.py @@ -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) diff --git a/account_banking_payment_transfer/model/payment_mode.py b/account_banking_payment_transfer/model/payment_mode.py index d45f56df9..3175e8fb1 100644 --- a/account_banking_payment_transfer/model/payment_mode.py +++ b/account_banking_payment_transfer/model/payment_mode.py @@ -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', diff --git a/account_banking_payment_transfer/model/payment_order_create.py b/account_banking_payment_transfer/model/payment_order_create.py index dd4f07fec..b0693da68 100644 --- a/account_banking_payment_transfer/model/payment_order_create.py +++ b/account_banking_payment_transfer/model/payment_order_create.py @@ -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' diff --git a/account_banking_payment_transfer/view/banking_transaction_wizard.xml b/account_banking_payment_transfer/view/banking_transaction_wizard.xml deleted file mode 100644 index f8b37cde9..000000000 --- a/account_banking_payment_transfer/view/banking_transaction_wizard.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - transaction.wizard - banking.transaction.wizard - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/account_banking_payment_transfer/view/payment_mode.xml b/account_banking_payment_transfer/view/payment_mode.xml index c46823c8d..96eef616b 100644 --- a/account_banking_payment_transfer/view/payment_mode.xml +++ b/account_banking_payment_transfer/view/payment_mode.xml @@ -29,6 +29,7 @@ /> + diff --git a/account_banking_payment_transfer/workflow/account_payment.xml b/account_banking_payment_transfer/workflow/account_payment.xml index 510a6610f..0a208b317 100644 --- a/account_banking_payment_transfer/workflow/account_payment.xml +++ b/account_banking_payment_transfer/workflow/account_payment.xml @@ -2,21 +2,21 @@ - + sent action_sent() function - + sent_wait write({'state': 'sent'}) function - + rejected action_rejected() @@ -26,30 +26,38 @@ write({'state':'rejected'}) - + sent - + - + done - - - + + + - - + + + test_done() done + + + + test_done() + account.move.line + get_transfer_move_line_ids() + - - - + + + rejected + action_done() +write({'state':'done'}) - + test_undo_done() undo_done + + + + test_undo_done() + account.move.line + get_transfer_move_line_ids() +