diff --git a/__unported__/account_banking/account_banking.py b/__unported__/account_banking/account_banking.py index 9aec2d8f8..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,35 +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 - ) - } - - -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/__unported__/account_banking_payment/__init__.py b/__unported__/account_banking_payment/__init__.py deleted file mode 100644 index 16e8b082f..000000000 --- a/__unported__/account_banking_payment/__init__.py +++ /dev/null @@ -1 +0,0 @@ -import model diff --git a/__unported__/account_banking_payment/model/__init__.py b/__unported__/account_banking_payment/model/__init__.py deleted file mode 100644 index 60600f542..000000000 --- a/__unported__/account_banking_payment/model/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -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 diff --git a/__unported__/account_banking_payment/model/account_payment.py b/__unported__/account_banking_payment/model/account_payment.py deleted file mode 100644 index 1e2e31277..000000000 --- a/__unported__/account_banking_payment/model/account_payment.py +++ /dev/null @@ -1,346 +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 _ -from openerp import netsvc - - -class payment_order(orm.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), - } - - 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) - - 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) - return True - - def set_done(self, cr, uid, ids, *args): - ''' - Extend standard transition to update children as well. - ''' - 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.")) - return True - - def test_undo_done(self, cr, uid, ids, context=None): - """ - Called from the workflow. Used to unset done state on - payment orders that were reconciled with bank transfers - which are being cancelled. - - 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. - """ - 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 - - def _prepare_transfer_move( - 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 - or line.communication), - } - return vals - - def _prepare_move_line_transfer_account( - self, cr, uid, order, line, 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), - 'move_id': move_id, - 'partner_id': False, - 'account_id': order.mode.transfer_account_id.id, - 'credit': (order.payment_order_type == 'payment' - and line.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), - } - return vals - - def _update_move_line_partner_account( - 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 - '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): - """This function is designed to be inherited""" - return - - def action_sent(self, cr, uid, ids, context=None): - """ - Create the moves that pay off the move lines from - the debit order. This happens when the debit order file is - generated. - """ - 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') - 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 - for line in order.line_ids: - # basic checks - if line.move_line_id and line.move_line_id.reconcile_id: - raise orm.except_orm( - _('Error'), - _('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 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 - 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 - payment_line_obj.write( - cr, uid, line.id, - {'transit_move_line_id': reconcile_move_line_id}, - context=context) - - 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) - account_move_obj.post(cr, uid, [move_id], context=context) - - # 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) - - return True diff --git a/__unported__/account_banking_payment/model/banking_import_transaction.py b/__unported__/account_banking_payment/model/banking_import_transaction.py deleted file mode 100644 index 48edd7979..000000000 --- a/__unported__/account_banking_payment/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/__unported__/account_banking_payment/model/banking_transaction_wizard.py b/__unported__/account_banking_payment/model/banking_transaction_wizard.py deleted file mode 100644 index dcc4d781b..000000000 --- a/__unported__/account_banking_payment/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/__unported__/account_banking_payment/view/account_payment.xml b/__unported__/account_banking_payment/view/account_payment.xml deleted file mode 100644 index fb29c8716..000000000 --- a/__unported__/account_banking_payment/view/account_payment.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - account.payment.order.form.banking-1 - - payment.order - - - - { - 'invisible':[('state','!=','draft')] - } - - - - - - - diff --git a/__unported__/account_banking_payment/view/banking_transaction_wizard.xml b/__unported__/account_banking_payment/view/banking_transaction_wizard.xml deleted file mode 100644 index f8b37cde9..000000000 --- a/__unported__/account_banking_payment/view/banking_transaction_wizard.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - transaction.wizard - banking.transaction.wizard - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/__unported__/account_banking_payment/view/payment_mode.xml b/__unported__/account_banking_payment/view/payment_mode.xml deleted file mode 100644 index c46823c8d..000000000 --- a/__unported__/account_banking_payment/view/payment_mode.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - payment.mode.form.inherit - payment.mode - - - - - - - - - - - - - - - - - - - - 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/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/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 diff --git a/account_banking_payment_export/models/account_move_line.py b/account_banking_payment_export/models/account_move_line.py index 74bb47f81..d8ca2fd1f 100644 --- a/account_banking_payment_export/models/account_move_line.py +++ b/account_banking_payment_export/models/account_move_line.py @@ -23,20 +23,37 @@ 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 + """ 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/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_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 new file mode 100644 index 000000000..9186ee3ad --- /dev/null +++ b/account_banking_payment_transfer/__init__.py @@ -0,0 +1 @@ +from . import model diff --git a/__unported__/account_banking_payment/__openerp__.py b/account_banking_payment_transfer/__openerp__.py similarity index 66% rename from __unported__/account_banking_payment/__openerp__.py rename to account_banking_payment_transfer/__openerp__.py index cd7621e7a..8c4501a2b 100644 --- a/__unported__/account_banking_payment/__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,27 @@ ############################################################################## { - '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', '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/__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/account_banking_payment_transfer/model/__init__.py b/account_banking_payment_transfer/model/__init__.py new file mode 100644 index 000000000..7a8cfda04 --- /dev/null +++ b/account_banking_payment_transfer/model/__init__.py @@ -0,0 +1,4 @@ +from . import account_payment +from . import payment_line +from . import payment_mode +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..701dd14a1 --- /dev/null +++ b/account_banking_payment_transfer/model/account_move_reconcile.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (C) 2014 ACSONE SA (). +# Copyright (C) 2014 Akretion (www.akretion.com) +# +# 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 models, workflow, api + + +class AccountMoveReconcile(models.Model): + _inherit = 'account.move.reconcile' + + @api.multi + def unlink(self): + """ + Workflow triggers upon unreconcile. This should go into the core. + """ + line_ids = [] + for reconcile in self: + for move_line in reconcile.line_id: + line_ids.append(move_line.id) + res = super(AccountMoveReconcile, self).unlink() + for line_id in line_ids: + 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 new file mode 100644 index 000000000..0051527e8 --- /dev/null +++ b/account_banking_payment_transfer/model/account_payment.py @@ -0,0 +1,255 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# 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 +# +# 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 models, fields, api, _ + + +class PaymentOrder(models.Model): + ''' + Enable extra states for payment exports + ''' + _inherit = 'payment.order' + + 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 + + @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 + + @api.multi + def _get_transfer_move_lines(self): + """ + Get the transfer move lines (on the transfer account). + """ + 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''' + return [move_line.id for move_line in self._get_transfer_move_lines()] + + @api.multi + def test_done(self): + """ + 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()]) + + @api.multi + def test_undo_done(self): + return not self.test_done() + + @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': self.mode.transfer_journal_id.id, + 'ref': '%s %s' % ( + self.payment_order_type[:3].upper(), self.reference) + } + return vals + + @api.model + def _prepare_move_line_transfer_account( + 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': 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': (self.payment_order_type == 'debit' + and amount or 0.0), + } + return vals + + @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[self.payment_order_type], line.name), + 'move_id': move.id, + 'partner_id': line.partner_id.id, + 'account_id': account_id, + 'credit': (self.payment_order_type == 'debit' + and line.amount or 0.0), + 'debit': (self.payment_order_type == 'payment' + and line.amount or 0.0), + } + return vals + + @api.model + def action_sent_no_move_line_hook(self, pay_line): + """This function is designed to be inherited""" + return + + @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. + """ + 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'), + } + 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] + + 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 + + # 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 + + # 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}) + + if line.move_line_id: + pl_obj.debit_reconcile(line.id) + else: + self.action_sent_no_move_line_hook(line) + + # 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) + + # post account move + move.post() + + # State field is written by act_sent_wait + self.write({'date_sent': fields.Date.context_today(self)}) + return True diff --git a/__unported__/account_banking_payment/model/payment_line.py b/account_banking_payment_transfer/model/payment_line.py similarity index 82% rename from __unported__/account_banking_payment/model/payment_line.py rename to account_banking_payment_transfer/model/payment_line.py index 0d41a966e..e55d170ef 100644 --- a/__unported__/account_banking_payment/model/payment_line.py +++ b/account_banking_payment_transfer/model/payment_line.py @@ -24,11 +24,11 @@ ############################################################################## from openerp.osv import orm, fields -from openerp import netsvc +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. @@ -36,16 +36,39 @@ 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 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 = { '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', + readonly=True, + help="Counterpart move line on the transfer account", ), } @@ -98,7 +121,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 +183,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 new file mode 100644 index 000000000..5fad56ced --- /dev/null +++ b/account_banking_payment_transfer/model/payment_mode.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# 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 +# +# 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 models, fields + + +class PaymentMode(models.Model): + _inherit = "payment.mode" + + 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') diff --git a/account_banking_payment_transfer/view/payment_mode.xml b/account_banking_payment_transfer/view/payment_mode.xml new file mode 100644 index 000000000..63cafb4bc --- /dev/null +++ b/account_banking_payment_transfer/view/payment_mode.xml @@ -0,0 +1,34 @@ + + + + + + + payment.mode.form.inherit + payment.mode + + + + + + + + + + + + + + diff --git a/__unported__/account_banking_payment/workflow/account_payment.xml b/account_banking_payment_transfer/workflow/account_payment.xml similarity index 59% rename from __unported__/account_banking_payment/workflow/account_payment.xml rename to account_banking_payment_transfer/workflow/account_payment.xml index 510a6610f..0a208b317 100644 --- a/__unported__/account_banking_payment/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() + 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 + + 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 73% rename from __unported__/account_banking_tests/tests/test_payment_roundtrip.py rename to account_banking_tests/tests/test_payment_roundtrip.py index ca42ab6fc..2212f6319 100644 --- a/__unported__/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 import netsvc +from openerp.tests.common import TransactionCase +from openerp import workflow -class TestPaymentRoundtrip(SingleTransactionCase): +class TestPaymentRoundtrip(TransactionCase): def assert_payment_order_state(self, expected): """ @@ -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', @@ -189,13 +189,13 @@ 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') - 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. @@ -235,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, }) @@ -242,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( @@ -281,8 +286,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') @@ -294,9 +298,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( @@ -306,13 +308,13 @@ 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. """ 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 +322,67 @@ 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': -100.0, + 'account_id': self.payable_id, + 'partner_id': self.supplier1, + }) + 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 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, }) - 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']}]) self.assert_payment_order_state('done') def check_reconciliations(self, reg, cr, uid): @@ -340,6 +390,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,9 +402,27 @@ 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): + """ 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) @@ -358,5 +430,21 @@ 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) + + 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) 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 @@ - ). # (C) 2011 - 2013 Therp BV (). +# (C) 2014 ACSONE SA/NV (). # # All other contributions are (C) by their respective contributors # @@ -23,21 +24,24 @@ # ############################################################################## -from openerp.osv import orm, fields +{ + '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. - -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'), - } + 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/__unported__/account_banking_payment/model/payment_mode.py b/account_payment_mode_term/models/payment_mode.py similarity index 70% rename from __unported__/account_banking_payment/model/payment_mode.py rename to account_payment_mode_term/models/payment_mode.py index d45f56df9..b37f0dede 100644 --- a/__unported__/account_banking_payment/model/payment_mode.py +++ b/account_payment_mode_term/models/payment_mode.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 # @@ -30,19 +31,6 @@ class payment_mode(orm.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'), - ), 'payment_term_ids': fields.many2many( 'account.payment.term', 'account_payment_order_terms_rel', 'mode_id', 'term_id', 'Payment terms', diff --git a/__unported__/account_banking_payment/model/payment_order_create.py b/account_payment_mode_term/models/payment_order_create.py similarity index 100% rename from __unported__/account_banking_payment/model/payment_order_create.py rename to account_payment_mode_term/models/payment_order_create.py 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..63c0671c6 --- /dev/null +++ b/account_payment_mode_term/views/payment_mode.xml @@ -0,0 +1,23 @@ + + + + + + + payment.mode.form.inherit + payment.mode + + + + + + + + + + + +