From 9c9826ee4be275b9f3c2b732eff487bc14fabf58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Tue, 16 Sep 2014 18:49:55 +0200 Subject: [PATCH 01/20] [IMP] move account.move.line.get_balance() to account_banking_payment_export --- .../account_banking/account_banking.py | 19 ---------- .../models/account_move_line.py | 35 ++++++++++++++----- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/__unported__/account_banking/account_banking.py b/__unported__/account_banking/account_banking.py index 9aec2d8f8..829befc29 100644 --- a/__unported__/account_banking/account_banking.py +++ b/__unported__/account_banking/account_banking.py @@ -662,22 +662,3 @@ class invoice(orm.Model): 'Reference Type', required=True ) } - - -class account_move_line(orm.Model): - _inherit = "account.move.line" - - def get_balance(self, cr, uid, ids, context=None): - """ - Return the balance of any set of move lines. - - Not to be confused with the 'balance' field on this model, which - returns the account balance that the move line applies to. - """ - total = 0.0 - if not ids: - return 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 diff --git a/account_banking_payment_export/models/account_move_line.py b/account_banking_payment_export/models/account_move_line.py index 74bb47f81..09b4f6140 100644 --- a/account_banking_payment_export/models/account_move_line.py +++ b/account_banking_payment_export/models/account_move_line.py @@ -23,18 +23,35 @@ from openerp.osv import orm, fields from operator import itemgetter -# All the code below aims at fixing one small issue in _to_pay_search() -# But _to_pay_search() is the search function of the field 'amount_to_pay' -# which is a field.function and these functions are not inheritable in OpenERP. -# So we have to inherit the field 'amount_to_pay' and duplicate the related -# functions -# If the patch that I proposed in this bug report -# https://bugs.launchpad.net/openobject-addons/+bug/1275478 -# is integrated in addons/account_payment, then we will be able to remove this -# file. -- Alexis de Lattre class AccountMoveLine(orm.Model): _inherit = 'account.move.line' + def get_balance(self, cr, uid, ids, context=None): + """ + Return the balance of any set of move lines. + + Not to be confused with the 'balance' field on this model, which + returns the account balance that the move line applies to. + """ + total = 0.0 + if not ids: + return 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 + + # All the code below aims at fixing one small issue in _to_pay_search() + # But _to_pay_search() is the search function of the field 'amount_to_pay' + # which is a field.function and these functions are not inheritable in + # OpenERP. + # So we have to inherit the field 'amount_to_pay' and duplicate the related + # functions + # If the patch that I proposed in this bug report + # https://bugs.launchpad.net/openobject-addons/+bug/1275478 + # is integrated in addons/account_payment, then we will be able to remove + # 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 (excepting cancelled orders)""" From 3eddb643f165850db87b3ea41076988dd66439d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Tue, 30 Sep 2014 18:09:49 +0200 Subject: [PATCH 02/20] [IMP] move 'structured' reference type to account_banking_payment_export --- .../account_banking/account_banking.py | 29 --------------- .../models/__init__.py | 1 + .../models/account_invoice.py | 35 +++++++++++++++++++ 3 files changed, 36 insertions(+), 29 deletions(-) create mode 100644 account_banking_payment_export/models/account_invoice.py diff --git a/__unported__/account_banking/account_banking.py b/__unported__/account_banking/account_banking.py index 829befc29..13d91242e 100644 --- a/__unported__/account_banking/account_banking.py +++ b/__unported__/account_banking/account_banking.py @@ -621,22 +621,6 @@ class account_bank_statement_line(orm.Model): class invoice(orm.Model): - ''' - Create other reference types as well. - - Descendant classes can extend this function to add more reference - types, ie. - - def _get_reference_type(self, cr, uid, context=None): - return super(my_class, self)._get_reference_type(cr, uid, - context=context) + [('my_ref', _('My reference')] - - Don't forget to redefine the column "reference_type" as below or - your method will never be triggered. - - TODO: move 'structured' part to account_banking_payment module - where it belongs - ''' _inherit = 'account.invoice' def test_undo_paid(self, cr, uid, ids, context=None): @@ -649,16 +633,3 @@ class invoice(orm.Model): return False return True - def _get_reference_type(self, cr, uid, context=None): - ''' - Return the list of reference types - ''' - return [('none', _('Free Reference')), - ('structured', _('Structured Reference')), - ] - - _columns = { - 'reference_type': fields.selection(_get_reference_type, - 'Reference Type', required=True - ) - } diff --git a/account_banking_payment_export/models/__init__.py b/account_banking_payment_export/models/__init__.py index 2f044680a..58f25e448 100644 --- a/account_banking_payment_export/models/__init__.py +++ b/account_banking_payment_export/models/__init__.py @@ -3,3 +3,4 @@ from . import account_payment from . import payment_mode from . import payment_mode_type from . import account_move_line +from . import account_invoice diff --git a/account_banking_payment_export/models/account_invoice.py b/account_banking_payment_export/models/account_invoice.py new file mode 100644 index 000000000..89da3b71b --- /dev/null +++ b/account_banking_payment_export/models/account_invoice.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (C) 2014 ACSONE SA (). +# +# 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 import api, models, _ + + +class AccountInvoice(models.Model): + _inherit = 'account.invoice' + + @api.model + def _get_reference_type(self): + rt = super(AccountInvoice, self)._get_reference_type() + rt.append(('structured', _('Structured Reference'))) + return rt From 95d2c0a4684163b9c7e82b4a68b0e4a99b7ba7b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Tue, 30 Sep 2014 18:11:22 +0200 Subject: [PATCH 03/20] [MIG] move account_banking_payment out of __unported__and rename to account_banking_payment_transfer --- .../__init__.py | 0 .../__openerp__.py | 0 .../i18n/account_banking_payment.pot | 0 .../i18n/nl.po | 0 .../model/__init__.py | 0 .../model/account_payment.py | 0 .../model/banking_import_line.py | 0 .../model/banking_import_transaction.py | 0 .../model/banking_transaction_wizard.py | 0 .../model/payment_line.py | 0 .../model/payment_mode.py | 0 .../model/payment_order_create.py | 0 .../view/account_payment.xml | 0 .../view/banking_transaction_wizard.xml | 0 .../view/payment_mode.xml | 0 .../workflow/account_payment.xml | 0 16 files changed, 0 insertions(+), 0 deletions(-) rename {__unported__/account_banking_payment => account_banking_payment_transfer}/__init__.py (100%) rename {__unported__/account_banking_payment => account_banking_payment_transfer}/__openerp__.py (100%) rename {__unported__/account_banking_payment => account_banking_payment_transfer}/i18n/account_banking_payment.pot (100%) rename {__unported__/account_banking_payment => account_banking_payment_transfer}/i18n/nl.po (100%) rename {__unported__/account_banking_payment => account_banking_payment_transfer}/model/__init__.py (100%) rename {__unported__/account_banking_payment => account_banking_payment_transfer}/model/account_payment.py (100%) rename {__unported__/account_banking_payment => account_banking_payment_transfer}/model/banking_import_line.py (100%) rename {__unported__/account_banking_payment => account_banking_payment_transfer}/model/banking_import_transaction.py (100%) rename {__unported__/account_banking_payment => account_banking_payment_transfer}/model/banking_transaction_wizard.py (100%) rename {__unported__/account_banking_payment => account_banking_payment_transfer}/model/payment_line.py (100%) rename {__unported__/account_banking_payment => account_banking_payment_transfer}/model/payment_mode.py (100%) rename {__unported__/account_banking_payment => account_banking_payment_transfer}/model/payment_order_create.py (100%) rename {__unported__/account_banking_payment => account_banking_payment_transfer}/view/account_payment.xml (100%) rename {__unported__/account_banking_payment => account_banking_payment_transfer}/view/banking_transaction_wizard.xml (100%) rename {__unported__/account_banking_payment => account_banking_payment_transfer}/view/payment_mode.xml (100%) rename {__unported__/account_banking_payment => account_banking_payment_transfer}/workflow/account_payment.xml (100%) diff --git a/__unported__/account_banking_payment/__init__.py b/account_banking_payment_transfer/__init__.py similarity index 100% rename from __unported__/account_banking_payment/__init__.py rename to account_banking_payment_transfer/__init__.py diff --git a/__unported__/account_banking_payment/__openerp__.py b/account_banking_payment_transfer/__openerp__.py similarity index 100% rename from __unported__/account_banking_payment/__openerp__.py rename to account_banking_payment_transfer/__openerp__.py diff --git a/__unported__/account_banking_payment/i18n/account_banking_payment.pot b/account_banking_payment_transfer/i18n/account_banking_payment.pot similarity index 100% rename from __unported__/account_banking_payment/i18n/account_banking_payment.pot rename to account_banking_payment_transfer/i18n/account_banking_payment.pot diff --git a/__unported__/account_banking_payment/i18n/nl.po b/account_banking_payment_transfer/i18n/nl.po similarity index 100% rename from __unported__/account_banking_payment/i18n/nl.po rename to account_banking_payment_transfer/i18n/nl.po diff --git a/__unported__/account_banking_payment/model/__init__.py b/account_banking_payment_transfer/model/__init__.py similarity index 100% rename from __unported__/account_banking_payment/model/__init__.py rename to account_banking_payment_transfer/model/__init__.py diff --git a/__unported__/account_banking_payment/model/account_payment.py b/account_banking_payment_transfer/model/account_payment.py similarity index 100% rename from __unported__/account_banking_payment/model/account_payment.py rename to account_banking_payment_transfer/model/account_payment.py diff --git a/__unported__/account_banking_payment/model/banking_import_line.py b/account_banking_payment_transfer/model/banking_import_line.py similarity index 100% rename from __unported__/account_banking_payment/model/banking_import_line.py rename to account_banking_payment_transfer/model/banking_import_line.py diff --git a/__unported__/account_banking_payment/model/banking_import_transaction.py b/account_banking_payment_transfer/model/banking_import_transaction.py similarity index 100% rename from __unported__/account_banking_payment/model/banking_import_transaction.py rename to account_banking_payment_transfer/model/banking_import_transaction.py diff --git a/__unported__/account_banking_payment/model/banking_transaction_wizard.py b/account_banking_payment_transfer/model/banking_transaction_wizard.py similarity index 100% rename from __unported__/account_banking_payment/model/banking_transaction_wizard.py rename to account_banking_payment_transfer/model/banking_transaction_wizard.py diff --git a/__unported__/account_banking_payment/model/payment_line.py b/account_banking_payment_transfer/model/payment_line.py similarity index 100% rename from __unported__/account_banking_payment/model/payment_line.py rename to account_banking_payment_transfer/model/payment_line.py diff --git a/__unported__/account_banking_payment/model/payment_mode.py b/account_banking_payment_transfer/model/payment_mode.py similarity index 100% rename from __unported__/account_banking_payment/model/payment_mode.py rename to account_banking_payment_transfer/model/payment_mode.py diff --git a/__unported__/account_banking_payment/model/payment_order_create.py b/account_banking_payment_transfer/model/payment_order_create.py similarity index 100% rename from __unported__/account_banking_payment/model/payment_order_create.py rename to account_banking_payment_transfer/model/payment_order_create.py diff --git a/__unported__/account_banking_payment/view/account_payment.xml b/account_banking_payment_transfer/view/account_payment.xml similarity index 100% rename from __unported__/account_banking_payment/view/account_payment.xml rename to account_banking_payment_transfer/view/account_payment.xml diff --git a/__unported__/account_banking_payment/view/banking_transaction_wizard.xml b/account_banking_payment_transfer/view/banking_transaction_wizard.xml similarity index 100% rename from __unported__/account_banking_payment/view/banking_transaction_wizard.xml rename to account_banking_payment_transfer/view/banking_transaction_wizard.xml diff --git a/__unported__/account_banking_payment/view/payment_mode.xml b/account_banking_payment_transfer/view/payment_mode.xml similarity index 100% rename from __unported__/account_banking_payment/view/payment_mode.xml rename to account_banking_payment_transfer/view/payment_mode.xml diff --git a/__unported__/account_banking_payment/workflow/account_payment.xml b/account_banking_payment_transfer/workflow/account_payment.xml similarity index 100% rename from __unported__/account_banking_payment/workflow/account_payment.xml rename to account_banking_payment_transfer/workflow/account_payment.xml 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 04/20] [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() + From cbb0f41426133cf5da6aab826b915aa0bc08cfc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Mon, 29 Sep 2014 22:06:15 +0200 Subject: [PATCH 05/20] [IMP] do not attempt to create transfer moves for payment lines without associated ar/ap move line Otherwise, it is not possible to execute such payment order if the payment mode has a transfer account. And I can't see how it could make sense if the transfer move cannot be reconciled in the first place. --- .../model/account_payment.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/account_banking_payment_transfer/model/account_payment.py b/account_banking_payment_transfer/model/account_payment.py index 4a56cfa1b..b027f99d6 100644 --- a/account_banking_payment_transfer/model/account_payment.py +++ b/account_banking_payment_transfer/model/account_payment.py @@ -207,12 +207,7 @@ class payment_order(orm.Model): self, cr, uid, order, line, vals, context=None): vals.update({ 'partner_id': line.partner_id.id, - 'account_id': (line.move_line_id - and line.move_line_id.account_id.id - or False), - # if not line.move_line_id, the field 'account_id' must be set by - # another module that inherit this function, like for example in - # the module purchase_payment_order + 'account_id': line.move_line_id.account_id.id, 'credit': (order.payment_order_type == 'debit' and line.amount or 0.0), 'debit': (order.payment_order_type == 'payment' @@ -242,8 +237,10 @@ class payment_order(orm.Model): or not order.mode.transfer_account_id: continue for line in order.line_ids: + if not line.move_line_id: + continue # basic checks - if line.move_line_id and line.move_line_id.reconcile_id: + if line.move_line_id.reconcile_id: raise orm.except_orm( _('Error'), _('Move line %s has already been paid/reconciled') From ad523cba58b76d69a484c769fb7226fad1ab41cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Mon, 29 Sep 2014 22:08:48 +0200 Subject: [PATCH 06/20] [IMP] workflow triggers when unreconciling move lines too As the workflows listening on account_move_line are triggered upon reconciliation, it makes sens to trigger them upon unreconcile too. --- .../model/__init__.py | 1 + .../model/account_move_reconcile.py | 45 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 account_banking_payment_transfer/model/account_move_reconcile.py diff --git a/account_banking_payment_transfer/model/__init__.py b/account_banking_payment_transfer/model/__init__.py index 9069347e8..ce4014976 100644 --- a/account_banking_payment_transfer/model/__init__.py +++ b/account_banking_payment_transfer/model/__init__.py @@ -2,3 +2,4 @@ from . import account_payment from . import payment_line from . import payment_mode from . import payment_order_create +from . import account_move_reconcile diff --git a/account_banking_payment_transfer/model/account_move_reconcile.py b/account_banking_payment_transfer/model/account_move_reconcile.py new file mode 100644 index 000000000..49f4f59ef --- /dev/null +++ b/account_banking_payment_transfer/model/account_move_reconcile.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (C) 2014 ACSONE SA (). +# +# 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 +from openerp import workflow + + +class AccountMoveReconcile(orm.Model): + + _inherit = 'account.move.reconcile' + + def unlink(self, cr, uid, ids, context=None): + """ workflow triggers upon unreconcile + + This should go into the core""" + line_ids = [] + for reconcile in self.browse(cr, uid, ids, context=context): + for move_line in reconcile.line_id: + line_ids.append(move_line.id) + res = super(AccountMoveReconcile, self).\ + unlink(cr, uid, ids, context=context) + for line_id in line_ids: + workflow.trg_trigger(uid, 'account.move.line', line_id, cr) + return res From 762fa5f4e9c1d05d3f693ed0527a4871f241b728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Tue, 30 Sep 2014 14:29:33 +0200 Subject: [PATCH 07/20] [MIG] migrate payment test suite --- .../__init__.py | 0 .../__openerp__.py | 4 ++-- .../tests/__init__.py | 2 +- .../tests/test_payment_roundtrip.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename {__unported__/account_banking_tests => account_banking_tests}/__init__.py (100%) rename {__unported__/account_banking_tests => account_banking_tests}/__openerp__.py (95%) rename {__unported__/account_banking_tests => account_banking_tests}/tests/__init__.py (55%) rename {__unported__/account_banking_tests => account_banking_tests}/tests/test_payment_roundtrip.py (99%) diff --git a/__unported__/account_banking_tests/__init__.py b/account_banking_tests/__init__.py similarity index 100% rename from __unported__/account_banking_tests/__init__.py rename to account_banking_tests/__init__.py diff --git a/__unported__/account_banking_tests/__openerp__.py b/account_banking_tests/__openerp__.py similarity index 95% rename from __unported__/account_banking_tests/__openerp__.py rename to account_banking_tests/__openerp__.py index fd35db0b2..d0945ed47 100644 --- a/__unported__/account_banking_tests/__openerp__.py +++ b/account_banking_tests/__openerp__.py @@ -27,7 +27,7 @@ 'category': 'Banking addons', 'depends': [ 'account_accountant', - 'account_banking_payment', + 'account_banking_payment_transfer', 'account_banking_sepa_credit_transfer', ], 'description': ''' @@ -37,5 +37,5 @@ dependencies installed, so that you can run the tests. If you only run the tests manually, you don't even have to install this module, only its dependencies. ''', - 'installable': False, + 'installable': True, } diff --git a/__unported__/account_banking_tests/tests/__init__.py b/account_banking_tests/tests/__init__.py similarity index 55% rename from __unported__/account_banking_tests/tests/__init__.py rename to account_banking_tests/tests/__init__.py index 2a658a292..0fdf0f213 100644 --- a/__unported__/account_banking_tests/tests/__init__.py +++ b/account_banking_tests/tests/__init__.py @@ -1,4 +1,4 @@ -import test_payment_roundtrip +from . import test_payment_roundtrip fast_suite = [ test_payment_roundtrip, diff --git a/__unported__/account_banking_tests/tests/test_payment_roundtrip.py b/account_banking_tests/tests/test_payment_roundtrip.py similarity index 99% rename from __unported__/account_banking_tests/tests/test_payment_roundtrip.py rename to account_banking_tests/tests/test_payment_roundtrip.py index ca42ab6fc..133e79bee 100644 --- a/__unported__/account_banking_tests/tests/test_payment_roundtrip.py +++ b/account_banking_tests/tests/test_payment_roundtrip.py @@ -358,5 +358,5 @@ class TestPaymentRoundtrip(SingleTransactionCase): self.setup_payment_config(reg, cr, uid) self.setup_payment(reg, cr, uid) self.export_payment(reg, cr, uid) - self.setup_bank_statement(reg, cr, uid) + # self.setup_bank_statement(reg, cr, uid) self.check_reconciliations(reg, cr, uid) From 92460ac7c91d215717505b5f56e8e90c7db3726d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Tue, 30 Sep 2014 17:45:22 +0200 Subject: [PATCH 08/20] [IMP] upgrade payment roundtrip test suite Support native v8 bank statement reconcile and extend it to test the reconciliation state of transfer moves. --- .../tests/test_payment_roundtrip.py | 64 +++++++++++++++---- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/account_banking_tests/tests/test_payment_roundtrip.py b/account_banking_tests/tests/test_payment_roundtrip.py index 133e79bee..5835ccdaa 100644 --- a/account_banking_tests/tests/test_payment_roundtrip.py +++ b/account_banking_tests/tests/test_payment_roundtrip.py @@ -121,7 +121,7 @@ class TestPaymentRoundtrip(SingleTransactionCase): can be validated properly. """ partner_model = reg('res.partner') - supplier1 = partner_model.create( + self.supplier1 = partner_model.create( cr, uid, { 'name': 'Supplier 1', 'supplier': True, @@ -135,7 +135,7 @@ class TestPaymentRoundtrip(SingleTransactionCase): }) ], }, context=context) - supplier2 = partner_model.create( + self.supplier2 = partner_model.create( cr, uid, { 'name': 'Supplier 2', 'supplier': True, @@ -160,7 +160,7 @@ class TestPaymentRoundtrip(SingleTransactionCase): invoice_model = reg('account.invoice') values = { 'type': 'in_invoice', - 'partner_id': supplier1, + 'partner_id': self.supplier1, 'account_id': self.payable_id, 'invoice_line': [ (0, False, { @@ -179,7 +179,7 @@ class TestPaymentRoundtrip(SingleTransactionCase): 'type': 'in_invoice', })] values.update({ - 'partner_id': supplier2, + 'partner_id': self.supplier2, 'name': 'Purchase 2', 'reference_type': 'structured', 'supplier_invoice_number': 'INV2', @@ -312,7 +312,6 @@ class TestPaymentRoundtrip(SingleTransactionCase): """ statement_model = reg('account.bank.statement') line_model = reg('account.bank.statement.line') - wizard_model = reg('banking.transaction.wizard') statement_id = statement_model.create( cr, uid, { 'name': 'Statement', @@ -320,19 +319,36 @@ class TestPaymentRoundtrip(SingleTransactionCase): 'balance_end_real': -200.0, 'period_id': reg('account.period').find(cr, uid)[0] }) - line_id = line_model.create( + line1_id = line_model.create( cr, uid, { 'name': 'Statement line', 'statement_id': statement_id, - 'amount': -200.0, + 'amount': -100.0, 'account_id': self.payable_id, + 'partner_id': self.supplier1, }) - wizard_id = wizard_model.create( - cr, uid, {'statement_line_id': line_id}) - wizard_model.write( - cr, uid, [wizard_id], { - 'manual_payment_order_id': self.payment_order_id}) - statement_model.button_confirm_bank(cr, uid, [statement_id]) + line1 = line_model.browse(cr, uid, line1_id) + rec_line1 = line_model.\ + get_reconciliation_proposition(cr, uid, line1)[0] + line_model.process_reconciliation(cr, uid, line1_id, [ + {'counterpart_move_line_id': rec_line1['id'], + 'debit': rec_line1['credit'], + 'credit': rec_line1['debit']}]) + line2_id = line_model.create( + cr, uid, { + 'name': 'Statement line', + 'statement_id': statement_id, + 'amount': -100.0, + 'account_id': self.payable_id, + 'partner_id': self.supplier2, + }) + line2 = line_model.browse(cr, uid, line2_id) + rec_line2 = line_model.\ + get_reconciliation_proposition(cr, uid, line2)[0] + line_model.process_reconciliation(cr, uid, line2_id, [ + {'counterpart_move_line_id': rec_line2['id'], + 'debit': rec_line2['credit'], + 'credit': rec_line2['debit']}]) self.assert_payment_order_state('done') def check_reconciliations(self, reg, cr, uid): @@ -340,6 +356,10 @@ class TestPaymentRoundtrip(SingleTransactionCase): Check if the payment order has any lines and that the transit move lines of those payment lines are reconciled by now. + + The transit move line is the line that pays the invoice + so it is reconciled as soon as the payment order + is sent. """ payment_order = reg('payment.order').browse( cr, uid, self.payment_order_id) @@ -348,6 +368,21 @@ class TestPaymentRoundtrip(SingleTransactionCase): assert line.transit_move_line_id, \ 'Payment order has no transfer move line' assert line.transit_move_line_id.reconcile_id, \ + 'Transit move line on payment line is not reconciled' + + def check_reconciliations_after_bank_statement(self, reg, cr, uid): + """ + Check if the payment order has any lines and that + the transfer move lines of those payment lines are + reconciled by now. + """ + payment_order = reg('payment.order').browse( + cr, uid, self.payment_order_id) + assert payment_order.line_ids, 'Payment order has no payment lines' + for line in payment_order.line_ids: + assert line.transfer_move_line_id, \ + 'Payment order has no transfer move line' + assert line.transfer_move_line_id.reconcile_id, \ 'Transfer move line on payment line is not reconciled' def test_payment_roundtrip(self): @@ -358,5 +393,6 @@ class TestPaymentRoundtrip(SingleTransactionCase): self.setup_payment_config(reg, cr, uid) self.setup_payment(reg, cr, uid) self.export_payment(reg, cr, uid) - # self.setup_bank_statement(reg, cr, uid) self.check_reconciliations(reg, cr, uid) + self.setup_bank_statement(reg, cr, uid) + self.check_reconciliations_after_bank_statement(reg, cr, uid) From d0d2b5ef62b0e68a579ebbc6bc080771b1d677bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Tue, 30 Sep 2014 19:16:31 +0200 Subject: [PATCH 09/20] [IMP] move the payment term filter to a standalone module account_payment_mode_term --- .../model/__init__.py | 1 - .../view/payment_mode.xml | 40 +++++++--------- account_payment_mode_term/__init__.py | 1 + account_payment_mode_term/__openerp__.py | 47 +++++++++++++++++++ account_payment_mode_term/models/__init__.py | 2 + .../models/payment_mode.py | 40 ++++++++++++++++ .../models}/payment_order_create.py | 3 -- .../views/payment_mode.xml | 24 ++++++++++ 8 files changed, 130 insertions(+), 28 deletions(-) create mode 100644 account_payment_mode_term/__init__.py create mode 100644 account_payment_mode_term/__openerp__.py create mode 100644 account_payment_mode_term/models/__init__.py create mode 100644 account_payment_mode_term/models/payment_mode.py rename {account_banking_payment_transfer/model => account_payment_mode_term/models}/payment_order_create.py (95%) create mode 100644 account_payment_mode_term/views/payment_mode.xml diff --git a/account_banking_payment_transfer/model/__init__.py b/account_banking_payment_transfer/model/__init__.py index ce4014976..7a8cfda04 100644 --- a/account_banking_payment_transfer/model/__init__.py +++ b/account_banking_payment_transfer/model/__init__.py @@ -1,5 +1,4 @@ from . import account_payment from . import payment_line from . import payment_mode -from . import payment_order_create from . import account_move_reconcile diff --git a/account_banking_payment_transfer/view/payment_mode.xml b/account_banking_payment_transfer/view/payment_mode.xml index 96eef616b..20770de32 100644 --- a/account_banking_payment_transfer/view/payment_mode.xml +++ b/account_banking_payment_transfer/view/payment_mode.xml @@ -3,7 +3,7 @@ payment.mode.form.inherit @@ -11,29 +11,21 @@ - - - - - - - - - - - + + + + diff --git a/account_payment_mode_term/__init__.py b/account_payment_mode_term/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/account_payment_mode_term/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/account_payment_mode_term/__openerp__.py b/account_payment_mode_term/__openerp__.py new file mode 100644 index 000000000..487443e26 --- /dev/null +++ b/account_payment_mode_term/__openerp__.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (C) 2009 EduSense BV (). +# (C) 2011 - 2013 Therp BV (). +# (C) 2014 ACSONE SA/NV (). +# +# 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 . +# +############################################################################## + +{ + 'name': 'Account Banking - Payments Term Filter', + 'version': '0.1.1', + 'license': 'AGPL-3', + 'author': 'Banking addons community', + 'website': 'https://github.com/OCA/banking', + 'category': 'Banking addons', + 'depends': [ + 'account_banking_payment_export', + ], + 'data': [ + 'views/payment_mode.xml', + ], + 'description': '''Payment term filter on payment mode. + + When set, only open invoices corresponding to the mode's + payment term are proposed when populating payment orders. + ''', + 'auto_install': False, + 'installable': True, +} diff --git a/account_payment_mode_term/models/__init__.py b/account_payment_mode_term/models/__init__.py new file mode 100644 index 000000000..5a3528e17 --- /dev/null +++ b/account_payment_mode_term/models/__init__.py @@ -0,0 +1,2 @@ +from . import payment_mode +from . import payment_order_create diff --git a/account_payment_mode_term/models/payment_mode.py b/account_payment_mode_term/models/payment_mode.py new file mode 100644 index 000000000..b37f0dede --- /dev/null +++ b/account_payment_mode_term/models/payment_mode.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (C) 2009 EduSense BV (). +# (C) 2011 - 2013 Therp BV (). +# (C) 2014 ACSONE SA/NV (). +# +# 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 payment_mode(orm.Model): + _inherit = "payment.mode" + + _columns = { + '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') + ), + } diff --git a/account_banking_payment_transfer/model/payment_order_create.py b/account_payment_mode_term/models/payment_order_create.py similarity index 95% rename from account_banking_payment_transfer/model/payment_order_create.py rename to account_payment_mode_term/models/payment_order_create.py index b0693da68..dd4f07fec 100644 --- a/account_banking_payment_transfer/model/payment_order_create.py +++ b/account_payment_mode_term/models/payment_order_create.py @@ -27,9 +27,6 @@ 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_payment_mode_term/views/payment_mode.xml b/account_payment_mode_term/views/payment_mode.xml new file mode 100644 index 000000000..20c6cf083 --- /dev/null +++ b/account_payment_mode_term/views/payment_mode.xml @@ -0,0 +1,24 @@ + + + + + + + payment.mode.form.inherit + payment.mode + + + + + + + + + + + + + From 376957c363fe807ede49e392392eca052e56409b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Wed, 15 Oct 2014 22:42:40 +0200 Subject: [PATCH 10/20] [FIX] account_banking_payment_transfer: do not set name on the transfer move There is no need to set the name on the transfer move as the same information is present in the move lines labels. Moreover setting the name of the move will prevent the system to number it according to the sequence. --- account_banking_payment_transfer/model/account_payment.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/account_banking_payment_transfer/model/account_payment.py b/account_banking_payment_transfer/model/account_payment.py index b027f99d6..10ffcaccc 100644 --- a/account_banking_payment_transfer/model/account_payment.py +++ b/account_banking_payment_transfer/model/account_payment.py @@ -171,10 +171,6 @@ class payment_order(orm.Model): self, cr, uid, order, line, labels, context=None): vals = { 'journal_id': order.mode.transfer_journal_id.id, - 'name': '%s %s' % (labels[order.payment_order_type], - line.move_line_id - and line.move_line_id.move_id.name - or line.communication), 'ref': '%s %s' % (order.payment_order_type[:3].upper(), line.move_line_id and line.move_line_id.move_id.name From fec7f9d2496735f9f87eb8c394c3dd1ff560c884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sun, 16 Nov 2014 18:08:15 +0100 Subject: [PATCH 11/20] [IMP] account_banking_tests: LocalService is now deprecated --- account_banking_tests/tests/test_payment_roundtrip.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/account_banking_tests/tests/test_payment_roundtrip.py b/account_banking_tests/tests/test_payment_roundtrip.py index 5835ccdaa..78dcc0087 100644 --- a/account_banking_tests/tests/test_payment_roundtrip.py +++ b/account_banking_tests/tests/test_payment_roundtrip.py @@ -19,7 +19,7 @@ ############################################################################## from datetime import datetime from openerp.tests.common import SingleTransactionCase -from openerp import netsvc +from openerp import workflow class TestPaymentRoundtrip(SingleTransactionCase): @@ -189,9 +189,8 @@ class TestPaymentRoundtrip(SingleTransactionCase): invoice_model.create( cr, uid, values, context={ 'type': 'in_invoice'})) - wf_service = netsvc.LocalService('workflow') for invoice_id in self.invoice_ids: - wf_service.trg_validate( + workflow.trg_validate( uid, 'account.invoice', invoice_id, 'invoice_open', cr) self.assert_invoices_state('open') @@ -281,8 +280,7 @@ class TestPaymentRoundtrip(SingleTransactionCase): 'No payment line created from invoice 2 or with the wrong ' 'communication') - wf_service = netsvc.LocalService('workflow') - wf_service.trg_validate( + workflow.trg_validate( uid, 'payment.order', self.payment_order_id, 'open', cr) self.assert_payment_order_state('open') From ff39774a96d09f87e6beee18daf36b31c1f0b0ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sun, 16 Nov 2014 18:09:42 +0100 Subject: [PATCH 12/20] [FIX] account_banking_payment_test: remove usage of non-existent msg_identification field --- account_banking_tests/tests/test_payment_roundtrip.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/account_banking_tests/tests/test_payment_roundtrip.py b/account_banking_tests/tests/test_payment_roundtrip.py index 78dcc0087..04c5d4e2a 100644 --- a/account_banking_tests/tests/test_payment_roundtrip.py +++ b/account_banking_tests/tests/test_payment_roundtrip.py @@ -292,9 +292,7 @@ class TestPaymentRoundtrip(SingleTransactionCase): """ export_model = reg('banking.export.sepa.wizard') export_id = export_model.create( - cr, uid, { - 'msg_identification': 'EXP001'}, - context={'active_ids': [self.payment_order_id]}) + cr, uid, {}, context={'active_ids': [self.payment_order_id]}) export_model.create_sepa( cr, uid, [export_id]) export_model.save_sepa( From d88abb929b773f762f6bf076c305e1fe58bbb45a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Sun, 16 Nov 2014 18:35:04 +0100 Subject: [PATCH 13/20] [IMP] CamelCase class names --- account_banking_payment_transfer/model/account_payment.py | 2 +- account_banking_payment_transfer/model/payment_line.py | 2 +- account_banking_payment_transfer/model/payment_mode.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/account_banking_payment_transfer/model/account_payment.py b/account_banking_payment_transfer/model/account_payment.py index 10ffcaccc..dd2941316 100644 --- a/account_banking_payment_transfer/model/account_payment.py +++ b/account_banking_payment_transfer/model/account_payment.py @@ -28,7 +28,7 @@ from openerp.osv import orm, fields from openerp.tools.translate import _ -class payment_order(orm.Model): +class PaymentOrder(orm.Model): ''' Enable extra states for payment exports ''' diff --git a/account_banking_payment_transfer/model/payment_line.py b/account_banking_payment_transfer/model/payment_line.py index 03bc427aa..4e8ccbc47 100644 --- a/account_banking_payment_transfer/model/payment_line.py +++ b/account_banking_payment_transfer/model/payment_line.py @@ -28,7 +28,7 @@ from openerp import workflow from openerp.tools.translate import _ -class payment_line(orm.Model): +class PaymentLine(orm.Model): ''' Add some fields; make destination bank account mandatory, as it makes no sense to send payments into thin air. diff --git a/account_banking_payment_transfer/model/payment_mode.py b/account_banking_payment_transfer/model/payment_mode.py index 3175e8fb1..905862d16 100644 --- a/account_banking_payment_transfer/model/payment_mode.py +++ b/account_banking_payment_transfer/model/payment_mode.py @@ -26,7 +26,7 @@ from openerp.osv import orm, fields -class payment_mode(orm.Model): +class PaymentMode(orm.Model): _inherit = "payment.mode" _columns = { From fd479fc8947f1aab28ce1f3427402dc95ba6ff49 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 19 Dec 2014 11:36:50 +0100 Subject: [PATCH 14/20] [IMP] Better form view for payment mode --- account_banking_payment_transfer/view/payment_mode.xml | 8 +++----- account_payment_mode_term/views/payment_mode.xml | 9 ++++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/account_banking_payment_transfer/view/payment_mode.xml b/account_banking_payment_transfer/view/payment_mode.xml index 20770de32..3516d5625 100644 --- a/account_banking_payment_transfer/view/payment_mode.xml +++ b/account_banking_payment_transfer/view/payment_mode.xml @@ -10,10 +10,8 @@ payment.mode - - - + + + - + From 1a90364d0fb2c464c04adc41c43db47f7a3b7279 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 19 Dec 2014 23:59:02 +0100 Subject: [PATCH 15/20] Transfer move: one transfer move for each payment.order with only 1 line in the transfer account for the total of the account move. Move the inherit of the 'Invoice' button of payment.order from account_banking_payment_transfer to account_banking_payment_export Demo data: Add a bank account + mandate on Agrolait, to be able to easily test multi-partner SEPA DD --- .../demo/banking_demo.xml | 17 ++++ .../views/account_payment.xml | 4 + .../__openerp__.py | 2 +- .../model/account_payment.py | 78 ++++++++++--------- .../view/account_payment.xml | 18 ++--- .../demo/sepa_direct_debit_demo.xml | 8 ++ 6 files changed, 76 insertions(+), 51 deletions(-) diff --git a/account_banking_payment_export/demo/banking_demo.xml b/account_banking_payment_export/demo/banking_demo.xml index b11367bb8..97d44b1e0 100644 --- a/account_banking_payment_export/demo/banking_demo.xml +++ b/account_banking_payment_export/demo/banking_demo.xml @@ -30,6 +30,14 @@ + + BNP Paribas Fortis Charleroi + GEBABEBB03A + Charleroi + + + + FR76 4242 4242 4242 4242 4242 424 iban @@ -57,6 +65,15 @@ FTNOFRP1XXX + + BE96 9988 7766 5544 + iban + + + BNP Paribas Fortis Charleroi + GEBABEBB03A + + diff --git a/account_banking_payment_export/views/account_payment.xml b/account_banking_payment_export/views/account_payment.xml index 3e9d35882..1ad55ce81 100644 --- a/account_banking_payment_export/views/account_payment.xml +++ b/account_banking_payment_export/views/account_payment.xml @@ -16,6 +16,10 @@ + + { + 'invisible': [('state', '!=', 'draft')]} + diff --git a/account_banking_payment_transfer/__openerp__.py b/account_banking_payment_transfer/__openerp__.py index 8ddfdbe0a..85d5f6d16 100644 --- a/account_banking_payment_transfer/__openerp__.py +++ b/account_banking_payment_transfer/__openerp__.py @@ -35,7 +35,7 @@ 'account_banking_payment_export', ], 'data': [ - # TODO: 'view/account_payment.xml', + 'view/account_payment.xml', 'view/payment_mode.xml', 'workflow/account_payment.xml', ], diff --git a/account_banking_payment_transfer/model/account_payment.py b/account_banking_payment_transfer/model/account_payment.py index dd2941316..4f1eb4c99 100644 --- a/account_banking_payment_transfer/model/account_payment.py +++ b/account_banking_payment_transfer/model/account_payment.py @@ -105,6 +105,8 @@ class PaymentOrder(orm.Model): ) ), 'date_sent': fields.date('Send date', readonly=True), + 'move_id': fields.many2one( + 'account.move', 'Transfer Move', readonly=True), } def _write_payment_lines(self, cr, uid, ids, **kwargs): @@ -168,47 +170,42 @@ class PaymentOrder(orm.Model): return not self.test_done(cr, uid, ids, context=context) def _prepare_transfer_move( - self, cr, uid, order, line, labels, context=None): + self, cr, uid, order, labels, context=None): vals = { 'journal_id': order.mode.transfer_journal_id.id, - 'ref': '%s %s' % (order.payment_order_type[:3].upper(), - line.move_line_id - and line.move_line_id.move_id.name - or line.communication), + 'ref': '%s %s' % ( + order.payment_order_type[:3].upper(), order.reference) } return vals def _prepare_move_line_transfer_account( - self, cr, uid, order, line, move_id, labels, context=None): + self, cr, uid, order, amount, move_id, labels, context=None): vals = { - 'name': _('%s for %s') % ( - labels[order.payment_order_type], - line.move_line_id and (line.move_line_id.invoice - and line.move_line_id.invoice.number - or line.move_line_id.name) - or line.communication), + 'name': '%s %s' % ( + labels[order.payment_order_type], order.reference), 'move_id': move_id, - 'partner_id': line.partner_id.id, + 'partner_id': False, 'account_id': order.mode.transfer_account_id.id, 'credit': (order.payment_order_type == 'payment' - and line.amount or 0.0), + and amount or 0.0), 'debit': (order.payment_order_type == 'debit' - and line.amount or 0.0), - 'date': fields.date.context_today( - self, cr, uid, context=context), + and amount or 0.0), } return vals - def _update_move_line_partner_account( - self, cr, uid, order, line, vals, context=None): - vals.update({ + def _prepare_move_line_partner_account( + self, cr, uid, order, line, move_id, labels, context=None): + vals = { + 'name': _('%s line %s') % ( + labels[order.payment_order_type], line.name), + 'move_id': move_id, 'partner_id': line.partner_id.id, 'account_id': line.move_line_id.account_id.id, 'credit': (order.payment_order_type == 'debit' and line.amount or 0.0), 'debit': (order.payment_order_type == 'payment' and line.amount or 0.0), - }) + } return vals def action_sent_no_move_line_hook(self, cr, uid, pay_line, context=None): @@ -232,6 +229,12 @@ class PaymentOrder(orm.Model): if not order.mode.transfer_journal_id \ or not order.mode.transfer_account_id: continue + + move_id = account_move_obj.create( + cr, uid, self._prepare_transfer_move( + cr, uid, order, labels, context=context), + context=context) + total_amount = 0 for line in order.line_ids: if not line.move_line_id: continue @@ -242,30 +245,21 @@ class PaymentOrder(orm.Model): _('Move line %s has already been paid/reconciled') % line.move_line_id.name) - move_id = account_move_obj.create( - cr, uid, self._prepare_transfer_move( - cr, uid, order, line, labels, context=context), - context=context) - # TODO: take multicurrency into 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 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) + partner_ml_vals = self._prepare_move_line_partner_account( + cr, uid, order, line, move_id, labels, context=context) + partner_move_line_id = account_move_line_obj.create( + cr, uid, partner_ml_vals, context=context) + total_amount += line.amount # 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}, + {'transit_move_line_id': partner_move_line_id}, context=context) if line.move_line_id: @@ -274,7 +268,17 @@ class PaymentOrder(orm.Model): else: self.action_sent_no_move_line_hook( cr, uid, line, context=context) - account_move_obj.post(cr, uid, [move_id], context=context) + + # create the payment/debit move line on the transfer account + trf_ml_vals = self._prepare_move_line_transfer_account( + cr, uid, order, total_amount, move_id, labels, + context=context) + account_move_line_obj.create(cr, uid, trf_ml_vals, context=context) + + # post account move + account_move_obj.post(cr, uid, [move_id], context=context) + # link transfer move to payment.order + order.write({'move_id': move_id}) # State field is written by act_sent_wait self.write(cr, uid, ids, { diff --git a/account_banking_payment_transfer/view/account_payment.xml b/account_banking_payment_transfer/view/account_payment.xml index fb29c8716..be357cffe 100644 --- a/account_banking_payment_transfer/view/account_payment.xml +++ b/account_banking_payment_transfer/view/account_payment.xml @@ -1,23 +1,15 @@ - - - account.payment.order.form.banking-1 + + account_banking_payment_transfer.add_move_id payment.order - - - { - 'invisible':[('state','!=','draft')] - } - - + + + diff --git a/account_banking_sepa_direct_debit/demo/sepa_direct_debit_demo.xml b/account_banking_sepa_direct_debit/demo/sepa_direct_debit_demo.xml index 041082d76..609824e68 100644 --- a/account_banking_sepa_direct_debit/demo/sepa_direct_debit_demo.xml +++ b/account_banking_sepa_direct_debit/demo/sepa_direct_debit_demo.xml @@ -23,5 +23,13 @@ valid + + + recurrent + first + + valid + + From b6555e2ea108465d46457ac72ae633b264eb7309 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Wed, 31 Dec 2014 08:50:51 +0100 Subject: [PATCH 16/20] account_banking_payment_transfer: add field transfer_move_option on payment.mode and adapt the generation of the transfer move account_banking_payment_transfer: port most python files to new API account_direct_debit: clean-up in invoice view --- .../__openerp__.py | 1 - .../model/account_move_reconcile.py | 24 +- .../model/account_payment.py | 359 ++++++++---------- .../model/payment_line.py | 19 +- .../model/payment_mode.py | 46 ++- .../view/account_payment.xml | 17 - .../view/payment_mode.xml | 1 + .../views/account_invoice.xml | 22 +- 8 files changed, 221 insertions(+), 268 deletions(-) delete mode 100644 account_banking_payment_transfer/view/account_payment.xml diff --git a/account_banking_payment_transfer/__openerp__.py b/account_banking_payment_transfer/__openerp__.py index 85d5f6d16..8c4501a2b 100644 --- a/account_banking_payment_transfer/__openerp__.py +++ b/account_banking_payment_transfer/__openerp__.py @@ -35,7 +35,6 @@ 'account_banking_payment_export', ], 'data': [ - 'view/account_payment.xml', 'view/payment_mode.xml', 'workflow/account_payment.xml', ], diff --git a/account_banking_payment_transfer/model/account_move_reconcile.py b/account_banking_payment_transfer/model/account_move_reconcile.py index 49f4f59ef..701dd14a1 100644 --- a/account_banking_payment_transfer/model/account_move_reconcile.py +++ b/account_banking_payment_transfer/model/account_move_reconcile.py @@ -2,6 +2,7 @@ ############################################################################## # # Copyright (C) 2014 ACSONE SA (). +# Copyright (C) 2014 Akretion (www.akretion.com) # # All other contributions are (C) by their respective contributors # @@ -22,24 +23,23 @@ # ############################################################################## -from openerp.osv import orm -from openerp import workflow +from openerp import models, workflow, api -class AccountMoveReconcile(orm.Model): - +class AccountMoveReconcile(models.Model): _inherit = 'account.move.reconcile' - def unlink(self, cr, uid, ids, context=None): - """ workflow triggers upon unreconcile - - This should go into the core""" + @api.multi + def unlink(self): + """ + Workflow triggers upon unreconcile. This should go into the core. + """ line_ids = [] - for reconcile in self.browse(cr, uid, ids, context=context): + for reconcile in self: for move_line in reconcile.line_id: line_ids.append(move_line.id) - res = super(AccountMoveReconcile, self).\ - unlink(cr, uid, ids, context=context) + res = super(AccountMoveReconcile, self).unlink() for line_id in line_ids: - workflow.trg_trigger(uid, 'account.move.line', line_id, cr) + workflow.trg_trigger( + self._uid, 'account.move.line', line_id, self._cr) return res diff --git a/account_banking_payment_transfer/model/account_payment.py b/account_banking_payment_transfer/model/account_payment.py index 4f1eb4c99..f741b17e0 100644 --- a/account_banking_payment_transfer/model/account_payment.py +++ b/account_banking_payment_transfer/model/account_payment.py @@ -4,6 +4,7 @@ # Copyright (C) 2009 EduSense BV (). # (C) 2011 - 2013 Therp BV (). # (C) 2014 ACSONE SA (). +# (C) 2014 Akretion (www.akretion.com) # # All other contributions are (C) by their respective contributors # @@ -24,139 +25,93 @@ # ############################################################################## -from openerp.osv import orm, fields -from openerp.tools.translate import _ +from openerp import models, fields, api, _ -class PaymentOrder(orm.Model): +class PaymentOrder(models.Model): ''' Enable extra states for payment exports ''' _inherit = 'payment.order' - _columns = { - 'date_scheduled': fields.date( - 'Scheduled date if fixed', - states={ - 'sent': [('readonly', True)], - 'rejected': [('readonly', True)], - 'done': [('readonly', True)] - }, - help='Select a date if you have chosen Preferred Date to be fixed.' - ), - 'reference': fields.char( - 'Reference', size=128, required=True, - states={ - 'sent': [('readonly', True)], - 'rejected': [('readonly', True)], - 'done': [('readonly', True)] - }, - ), - 'mode': fields.many2one( - 'payment.mode', 'Payment mode', select=True, required=True, - states={ - 'sent': [('readonly', True)], - 'rejected': [('readonly', True)], - 'done': [('readonly', True)] - }, - help='Select the Payment Mode to be applied.', - ), - 'state': fields.selection([ - ('draft', 'Draft'), - ('open', 'Confirmed'), - ('cancel', 'Cancelled'), - ('sent', 'Sent'), - ('rejected', 'Rejected'), - ('done', 'Done'), - ], 'State', select=True - ), - 'line_ids': fields.one2many( - 'payment.line', 'order_id', 'Payment lines', - states={ - 'open': [('readonly', True)], - 'cancel': [('readonly', True)], - 'sent': [('readonly', True)], - 'rejected': [('readonly', True)], - 'done': [('readonly', True)] - }, - ), - 'user_id': fields.many2one( - 'res.users', 'User', required=True, - states={ - 'sent': [('readonly', True)], - 'rejected': [('readonly', True)], - 'done': [('readonly', True)] - }, - ), - 'date_prefered': fields.selection([ - ('now', 'Directly'), - ('due', 'Due date'), - ('fixed', 'Fixed date') - ], "Preferred date", change_default=True, required=True, - states={ - 'sent': [('readonly', True)], - 'rejected': [('readonly', True)], - 'done': [('readonly', True)] - }, - help=("Choose an option for the Payment Order:'Fixed' stands for " - "a date specified by you.'Directly' stands for the direct " - "execution.'Due date' stands for the scheduled date of " - "execution." - ) - ), - 'date_sent': fields.date('Send date', readonly=True), - 'move_id': fields.many2one( - 'account.move', 'Transfer Move', readonly=True), - } - - def _write_payment_lines(self, cr, uid, ids, **kwargs): - ''' - ORM method for setting attributes of corresponding payment.line - objects. - Note that while this is ORM compliant, it is also very ineffecient due - to the absence of filters on writes and hence the requirement to - filter on the client(=OpenERP server) side. - ''' - if not hasattr(ids, '__iter__'): - ids = [ids] - payment_line_obj = self.pool.get('payment.line') - line_ids = payment_line_obj.search( - cr, uid, [ - ('order_id', 'in', ids) - ]) - payment_line_obj.write(cr, uid, line_ids, kwargs) + date_scheduled = fields.Date(states={ + 'sent': [('readonly', True)], + 'rejected': [('readonly', True)], + 'done': [('readonly', True)], + }) + reference = fields.Char(states={ + 'sent': [('readonly', True)], + 'rejected': [('readonly', True)], + 'done': [('readonly', True)], + }) + mode = fields.Many2one(states={ + 'sent': [('readonly', True)], + 'rejected': [('readonly', True)], + 'done': [('readonly', True)], + }) + state = fields.Selection([ + ('draft', 'Draft'), + ('open', 'Confirmed'), + ('cancel', 'Cancelled'), + ('sent', 'Sent'), + ('rejected', 'Rejected'), + ('done', 'Done'), + ], string='State') + line_ids = fields.One2many(states={ + 'open': [('readonly', True)], + 'cancel': [('readonly', True)], + 'sent': [('readonly', True)], + 'rejected': [('readonly', True)], + 'done': [('readonly', True)] + }) + user_id = fields.Many2one(states={ + 'sent': [('readonly', True)], + 'rejected': [('readonly', True)], + 'done': [('readonly', True)] + }) + date_prefered = fields.Selection(states={ + 'sent': [('readonly', True)], + 'rejected': [('readonly', True)], + 'done': [('readonly', True)] + }) + date_sent = fields.Date(string='Send date', readonly=True) def action_rejected(self, cr, uid, ids, context=None): return True - 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, - context=context)) - self.write(cr, uid, ids, - {'date_done': fields.date. - context_today(self, cr, uid, context=context)}) + @api.multi + def action_done(self): + for line in self.line_ids: + line.date_done = fields.Date.context_today(self) + self.date_done = fields.Date.context_today(self) # state is written in workflow definition return True - def _get_transfer_move_lines(self, cr, uid, ids, context=None): + @api.one + @api.returns('account.move.line') + def _get_transfer_move_lines(self): """ 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: - move_line = order_line.transfer_move_line_id - if move_line: - res.append(move_line) - return res + for pay_line in self.line_ids: + move_line = pay_line.transfer_move_line_id + if move_line: + return move_line + return False - 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)] + @api.multi + def get_transfer_move_line_ids(self, *args): + '''Used in the workflow for trigger_expr_id''' + print "self._get_transfer_move_lines=", self._get_transfer_move_lines() + # TODO I don't understand why self._get_transfer_move_lines() + # returns a single recordset and not a list of recordset + # I wanted to write + # return self._get_transfer_move_lines().ids + # but it doesn't work, so I wrote this below: + return [self._get_transfer_move_lines().id] - def test_done(self, cr, uid, ids, context=None): + @api.multi + def test_done(self): """ Test if all moves on the transfer account are reconciled. @@ -164,126 +119,142 @@ class PaymentOrder(orm.Model): 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)]) + self._get_transfer_move_lines()]) - def test_undo_done(self, cr, uid, ids, context=None): - return not self.test_done(cr, uid, ids, context=context) + @api.multi + def test_undo_done(self): + return not self.test_done() - def _prepare_transfer_move( - self, cr, uid, order, labels, context=None): + @api.model + def _prepare_transfer_move(self): + # TODO question : can I use self.mode.xxx in an @api.model ?? + # It works, but I'm not sure we are supposed to do that ! + # I didn't want to use api.one to avoid having to + # do self._prepare_transfer_move()[0] in action_sent + # I prefer to just have to do self._prepare_transfer_move() vals = { - 'journal_id': order.mode.transfer_journal_id.id, + 'journal_id': self.mode.transfer_journal_id.id, 'ref': '%s %s' % ( - order.payment_order_type[:3].upper(), order.reference) + self.payment_order_type[:3].upper(), self.reference) } return vals + @api.model def _prepare_move_line_transfer_account( - self, cr, uid, order, amount, move_id, labels, context=None): + self, amount, move, payment_lines, labels): + if len(payment_lines) == 1: + partner_id = payment_lines[0].partner_id.id + name = _('%s line %s') % ( + labels[self.payment_order_type], payment_lines[0].name) + else: + partner_id = False + name = '%s %s' % ( + labels[self.payment_order_type], self.reference) vals = { - 'name': '%s %s' % ( - labels[order.payment_order_type], order.reference), - 'move_id': move_id, - 'partner_id': False, - 'account_id': order.mode.transfer_account_id.id, - 'credit': (order.payment_order_type == 'payment' + 'name': name, + 'move_id': move.id, + 'partner_id': partner_id, + 'account_id': self.mode.transfer_account_id.id, + 'credit': (self.payment_order_type == 'payment' and amount or 0.0), - 'debit': (order.payment_order_type == 'debit' + 'debit': (self.payment_order_type == 'debit' and amount or 0.0), } return vals - def _prepare_move_line_partner_account( - self, cr, uid, order, line, move_id, labels, context=None): + @api.model + def _prepare_move_line_partner_account(self, line, move, labels): + if line.move_line_id: + account_id = line.move_line_id.account_id.id + else: + if self.payment_order_type == 'debit': + account_id = line.partner_id.property_account_receivable.id + else: + account_id = line.partner_id.property_account_payable.id vals = { 'name': _('%s line %s') % ( - labels[order.payment_order_type], line.name), - 'move_id': move_id, + labels[self.payment_order_type], line.name), + 'move_id': move.id, 'partner_id': line.partner_id.id, - 'account_id': line.move_line_id.account_id.id, - 'credit': (order.payment_order_type == 'debit' + 'account_id': account_id, + 'credit': (self.payment_order_type == 'debit' and line.amount or 0.0), - 'debit': (order.payment_order_type == 'payment' + 'debit': (self.payment_order_type == 'payment' and line.amount or 0.0), } return vals - def action_sent_no_move_line_hook(self, cr, uid, pay_line, context=None): + @api.model + def action_sent_no_move_line_hook(self, pay_line): """This function is designed to be inherited""" return - def action_sent(self, cr, uid, ids, context=None): + @api.one + def action_sent(self): """ Create the moves that pay off the move lines from the debit order. This happens when the debit order file is generated. """ - 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') + am_obj = self.env['account.move'] + aml_obj = self.env['account.move.line'] + pl_obj = self.env['payment.line'] labels = { 'payment': _('Payment order'), 'debit': _('Direct debit order'), } - for order in self.browse(cr, uid, ids, context=context): - if not order.mode.transfer_journal_id \ - or not order.mode.transfer_account_id: - continue + if self.mode.transfer_journal_id and self.mode.transfer_account_id: + # prepare a dict "trfmoves" that can be used when + # self.mode.transfer_move_option = date or line + # key = unique identifier (date or True or line.id) + # value = [pay_line1, pay_line2, ...] + trfmoves = {} + if self.mode.transfer_move_option == 'line': + for line in self.line_ids: + trfmoves[line.id] = [line] + else: + if self.date_prefered in ('now', 'fixed'): + trfmoves[True] = [] + for line in self.line_ids: + trfmoves[True].append(line) + else: # date_prefered == due + for line in self.line_ids: + if line.date in trfmoves: + trfmoves[line.date].append(line) + else: + trfmoves[line.date] = [line] - move_id = account_move_obj.create( - cr, uid, self._prepare_transfer_move( - cr, uid, order, labels, context=context), - context=context) - total_amount = 0 - for line in order.line_ids: - if not line.move_line_id: - continue - # basic checks - if line.move_line_id.reconcile_id: - raise orm.except_orm( - _('Error'), - _('Move line %s has already been paid/reconciled') - % line.move_line_id.name) + for identifier, lines in trfmoves.iteritems(): + mvals = self._prepare_transfer_move() + move = am_obj.create(mvals) + total_amount = 0 + for line in lines: + # TODO: take multicurrency into account - # TODO: take multicurrency into account + # create the payment/debit counterpart move line + # on the partner account + partner_ml_vals = self._prepare_move_line_partner_account( + line, move, labels) + partner_move_line = aml_obj.create(partner_ml_vals) + total_amount += line.amount - # create the payment/debit counterpart move line - # on the partner account - partner_ml_vals = self._prepare_move_line_partner_account( - cr, uid, order, line, move_id, labels, context=context) - partner_move_line_id = account_move_line_obj.create( - cr, uid, partner_ml_vals, context=context) - total_amount += line.amount + # register the payment/debit move line + # on the payment line and call reconciliation on it + line.write({'transit_move_line_id': partner_move_line.id}) - # 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': partner_move_line_id}, - context=context) + if line.move_line_id: + pl_obj.debit_reconcile(line.id) + else: + self.action_sent_no_move_line_hook(line) - if line.move_line_id: - payment_line_obj.debit_reconcile( - cr, uid, line.id, context=context) - else: - self.action_sent_no_move_line_hook( - cr, uid, line, context=context) + # create the payment/debit move line on the transfer account + trf_ml_vals = self._prepare_move_line_transfer_account( + total_amount, move, lines, labels) + aml_obj.create(trf_ml_vals) - # create the payment/debit move line on the transfer account - trf_ml_vals = self._prepare_move_line_transfer_account( - cr, uid, order, total_amount, move_id, labels, - context=context) - account_move_line_obj.create(cr, uid, trf_ml_vals, context=context) - - # post account move - account_move_obj.post(cr, uid, [move_id], context=context) - # link transfer move to payment.order - order.write({'move_id': move_id}) + # post account move + move.post() # State field is written by act_sent_wait - self.write(cr, uid, ids, { - 'date_sent': fields.date.context_today( - self, cr, uid, context=context), - }, context=context) - + self.write({'date_sent': fields.Date.context_today(self)}) return True diff --git a/account_banking_payment_transfer/model/payment_line.py b/account_banking_payment_transfer/model/payment_line.py index 4e8ccbc47..e55d170ef 100644 --- a/account_banking_payment_transfer/model/payment_line.py +++ b/account_banking_payment_transfer/model/payment_line.py @@ -40,13 +40,16 @@ class PaymentLine(orm.Model): 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 + if order_line.transit_move_line_id: + order_type = order_line.order_id.payment_order_type + trf_lines = order_line.transit_move_line_id.move_id.line_id + for move_line in trf_lines: + if order_type == 'debit' and move_line.debit > 0: + res[order_line.id] = move_line.id + elif order_type == 'payment' and move_line.credit > 0: + res[order_line.id] = move_line.id + else: + res[order_line.id] = False return res _columns = { @@ -64,7 +67,7 @@ class PaymentLine(orm.Model): type='many2one', relation='account.move.line', string='Transfer move line counterpart', - read_only=True, + readonly=True, help="Counterpart move line on the transfer account", ), } diff --git a/account_banking_payment_transfer/model/payment_mode.py b/account_banking_payment_transfer/model/payment_mode.py index 905862d16..947eea3f0 100644 --- a/account_banking_payment_transfer/model/payment_mode.py +++ b/account_banking_payment_transfer/model/payment_mode.py @@ -3,6 +3,7 @@ # # Copyright (C) 2009 EduSense BV (). # (C) 2011 - 2013 Therp BV (). +# (C) 2014 Akretion (www.akretion.com) # # All other contributions are (C) by their respective contributors # @@ -23,31 +24,28 @@ # ############################################################################## -from openerp.osv import orm, fields +from openerp import models, fields -class PaymentMode(orm.Model): +class PaymentMode(models.Model): _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. 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'), - ), - # 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', - help=('Limit selected invoices to invoices with these payment ' - 'terms') - ), - } + transfer_account_id = fields.Many2one( + 'account.account', string='Transfer account', + domain=[('type', '=', 'other'), ('reconcile', '=', True)], + help='Pay off lines in sent orders with a move on this ' + 'account. You can only select accounts of type regular ' + 'that are marked for reconciliation') + transfer_journal_id = fields.Many2one( + 'account.journal', string='Transfer journal', + help='Journal to write payment entries when confirming ' + 'a debit order of this mode') + transfer_move_option = fields.Selection([ + ('date', 'One move per payment date'), + ('line', 'One move per payment line'), + ], string='Transfer move option', default='date') + # 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', string='Payment terms', + help='Limit selected invoices to invoices with these payment terms') diff --git a/account_banking_payment_transfer/view/account_payment.xml b/account_banking_payment_transfer/view/account_payment.xml deleted file mode 100644 index be357cffe..000000000 --- a/account_banking_payment_transfer/view/account_payment.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - account_banking_payment_transfer.add_move_id - - payment.order - - - - - - - - - diff --git a/account_banking_payment_transfer/view/payment_mode.xml b/account_banking_payment_transfer/view/payment_mode.xml index 3516d5625..63cafb4bc 100644 --- a/account_banking_payment_transfer/view/payment_mode.xml +++ b/account_banking_payment_transfer/view/payment_mode.xml @@ -24,6 +24,7 @@ + diff --git a/account_direct_debit/views/account_invoice.xml b/account_direct_debit/views/account_invoice.xml index ece0e8d6e..76a46e128 100644 --- a/account_direct_debit/views/account_invoice.xml +++ b/account_direct_debit/views/account_invoice.xml @@ -6,20 +6,18 @@ account.invoice - - - + + - @@ -29,7 +27,7 @@ - Date: Tue, 6 Jan 2015 15:19:57 +0100 Subject: [PATCH 17/20] remove print statement --- account_banking_payment_transfer/model/account_payment.py | 1 - 1 file changed, 1 deletion(-) diff --git a/account_banking_payment_transfer/model/account_payment.py b/account_banking_payment_transfer/model/account_payment.py index f741b17e0..b40f99d0f 100644 --- a/account_banking_payment_transfer/model/account_payment.py +++ b/account_banking_payment_transfer/model/account_payment.py @@ -102,7 +102,6 @@ class PaymentOrder(models.Model): @api.multi def get_transfer_move_line_ids(self, *args): '''Used in the workflow for trigger_expr_id''' - print "self._get_transfer_move_lines=", self._get_transfer_move_lines() # TODO I don't understand why self._get_transfer_move_lines() # returns a single recordset and not a list of recordset # I wanted to write From ec0eb0a0fce51e810ebfbae5cef2d0339b2f3625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Tue, 6 Jan 2015 15:25:41 +0100 Subject: [PATCH 18/20] [FIX] account_banking_payment_transfer: remove a field that has been moved payment_term_ids is now in account_payment_mode_term --- account_banking_payment_transfer/model/payment_mode.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/account_banking_payment_transfer/model/payment_mode.py b/account_banking_payment_transfer/model/payment_mode.py index 947eea3f0..5fad56ced 100644 --- a/account_banking_payment_transfer/model/payment_mode.py +++ b/account_banking_payment_transfer/model/payment_mode.py @@ -44,8 +44,3 @@ class PaymentMode(models.Model): ('date', 'One move per payment date'), ('line', 'One move per payment line'), ], string='Transfer move option', default='date') - # 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', string='Payment terms', - help='Limit selected invoices to invoices with these payment terms') From 0ba85d8ef708bc89282d15db2d26d4344ec6baf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Bidoul?= Date: Tue, 6 Jan 2015 17:10:24 +0100 Subject: [PATCH 19/20] [IMP] account_banking_tests: adapt and extend test suite for grouped transfer moves --- .../tests/test_payment_roundtrip.py | 64 +++++++++++++++++-- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/account_banking_tests/tests/test_payment_roundtrip.py b/account_banking_tests/tests/test_payment_roundtrip.py index 04c5d4e2a..2212f6319 100644 --- a/account_banking_tests/tests/test_payment_roundtrip.py +++ b/account_banking_tests/tests/test_payment_roundtrip.py @@ -18,11 +18,11 @@ # ############################################################################## from datetime import datetime -from openerp.tests.common import SingleTransactionCase +from openerp.tests.common import TransactionCase from openerp import workflow -class TestPaymentRoundtrip(SingleTransactionCase): +class TestPaymentRoundtrip(TransactionCase): def assert_payment_order_state(self, expected): """ @@ -194,7 +194,8 @@ class TestPaymentRoundtrip(SingleTransactionCase): uid, 'account.invoice', invoice_id, 'invoice_open', cr) self.assert_invoices_state('open') - def setup_payment_config(self, reg, cr, uid): + def setup_payment_config(self, reg, cr, uid, + transfer_move_option='line'): """ Configure an additional account and journal for payments in transit and configure a payment mode with them. @@ -234,6 +235,7 @@ class TestPaymentRoundtrip(SingleTransactionCase): 'company_id': self.company_id, 'transfer_account_id': transfer_account_id, 'transfer_journal_id': transfer_journal_id, + 'transfer_move_option': transfer_move_option, 'type': payment_mode_type_id, }) @@ -241,11 +243,15 @@ class TestPaymentRoundtrip(SingleTransactionCase): """ Create a payment order with the invoices' payable move lines. Check that the payment order can be confirmed. + + date_preferred is set to 'now', to ensure one transfer move + when transfer_move_option = 'date'. """ self.payment_order_id = reg('payment.order').create( cr, uid, { 'reference': 'PAY001', 'mode': self.payment_mode_id, + 'date_prefered': 'now', }) context = {'active_id': self.payment_order_id} entries = reg('account.move.line').search( @@ -302,7 +308,8 @@ class TestPaymentRoundtrip(SingleTransactionCase): def setup_bank_statement(self, reg, cr, uid): """ - Create a bank statement with a single line. Call the reconciliation + Create a bank statement with a one line for each + payment order line. Call the reconciliation wizard to match the line with the open payment order. Confirm the bank statement. Check if the payment order is done. """ @@ -347,6 +354,37 @@ class TestPaymentRoundtrip(SingleTransactionCase): 'credit': rec_line2['debit']}]) self.assert_payment_order_state('done') + def setup_bank_statement_one_move(self, reg, cr, uid): + """ + Create a bank statement with a single line. Call the reconciliation + wizard to match the line with the open payment order. Confirm the + bank statement. Check if the payment order is done. + """ + statement_model = reg('account.bank.statement') + line_model = reg('account.bank.statement.line') + statement_id = statement_model.create( + cr, uid, { + 'name': 'Statement', + 'journal_id': self.bank_journal_id, + 'balance_end_real': -200.0, + 'period_id': reg('account.period').find(cr, uid)[0] + }) + line1_id = line_model.create( + cr, uid, { + 'name': 'Statement line', + 'statement_id': statement_id, + 'amount': -200.0, + 'account_id': self.payable_id, + }) + line1 = line_model.browse(cr, uid, line1_id) + rec_line1 = line_model.\ + get_reconciliation_proposition(cr, uid, line1)[0] + line_model.process_reconciliation(cr, uid, line1_id, [ + {'counterpart_move_line_id': rec_line1['id'], + 'debit': rec_line1['credit'], + 'credit': rec_line1['debit']}]) + self.assert_payment_order_state('done') + def check_reconciliations(self, reg, cr, uid): """ Check if the payment order has any lines and that @@ -382,6 +420,9 @@ class TestPaymentRoundtrip(SingleTransactionCase): 'Transfer move line on payment line is not reconciled' def test_payment_roundtrip(self): + """ Payment round trip using transfer account, + with one move per payment order line on the transfer account + """ reg, cr, uid, = self.registry, self.cr, self.uid self.setup_company(reg, cr, uid) self.setup_chart(reg, cr, uid) @@ -392,3 +433,18 @@ class TestPaymentRoundtrip(SingleTransactionCase): self.check_reconciliations(reg, cr, uid) self.setup_bank_statement(reg, cr, uid) self.check_reconciliations_after_bank_statement(reg, cr, uid) + + def test_payment_roundtrip_one_move(self): + """ Payment round trip using transfer account, + with one move per payment order on the transfer account + """ + reg, cr, uid, = self.registry, self.cr, self.uid + self.setup_company(reg, cr, uid) + self.setup_chart(reg, cr, uid) + self.setup_payables(reg, cr, uid) + self.setup_payment_config(reg, cr, uid, transfer_move_option='date') + self.setup_payment(reg, cr, uid) + self.export_payment(reg, cr, uid) + self.check_reconciliations(reg, cr, uid) + self.setup_bank_statement_one_move(reg, cr, uid) + self.check_reconciliations_after_bank_statement(reg, cr, uid) From c4aa30a6a8e73acb4bbfe20fd5fe3e0241a05d16 Mon Sep 17 00:00:00 2001 From: "Adrien Peiffer (ACSONE)" Date: Tue, 20 Jan 2015 10:18:24 +0100 Subject: [PATCH 20/20] [FIX] Use api.multi for _get_transfer_move_lines instead of api.one and return directly an empty list or a list of recordset --- .../model/account_payment.py | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/account_banking_payment_transfer/model/account_payment.py b/account_banking_payment_transfer/model/account_payment.py index b40f99d0f..0051527e8 100644 --- a/account_banking_payment_transfer/model/account_payment.py +++ b/account_banking_payment_transfer/model/account_payment.py @@ -87,27 +87,23 @@ class PaymentOrder(models.Model): # state is written in workflow definition return True - @api.one - @api.returns('account.move.line') + @api.multi def _get_transfer_move_lines(self): """ Get the transfer move lines (on the transfer account). """ - for pay_line in self.line_ids: - move_line = pay_line.transfer_move_line_id - if move_line: - return move_line - return False + res = [] + for order in self: + for order_line in order.line_ids: + move_line = order_line.transfer_move_line_id + if move_line: + res.append(move_line) + return res @api.multi def get_transfer_move_line_ids(self, *args): '''Used in the workflow for trigger_expr_id''' - # TODO I don't understand why self._get_transfer_move_lines() - # returns a single recordset and not a list of recordset - # I wanted to write - # return self._get_transfer_move_lines().ids - # but it doesn't work, so I wrote this below: - return [self._get_transfer_move_lines().id] + return [move_line.id for move_line in self._get_transfer_move_lines()] @api.multi def test_done(self):