diff --git a/account_banking_payment/model/account_payment.py b/account_banking_payment/model/account_payment.py index 1e05e9a77..a0428a312 100644 --- a/account_banking_payment/model/account_payment.py +++ b/account_banking_payment/model/account_payment.py @@ -181,18 +181,6 @@ class payment_order(orm.Model): cr, uid, ids, *args ) - def action_sent(self, cr, uid, ids, context=None): - ''' - Set both self and payment lines to state 'sent'. - ''' - self._write_payment_lines(cr, uid, ids, export_state='sent') - self.write(cr, uid, ids, { - 'state': 'sent', - 'date_sent': fields.date.context_today( - self, cr, uid, context=context), - }, context=context) - return True - def action_rejected(self, cr, uid, ids, *args): ''' Set both self and payment lines to state 'rejected'. @@ -215,26 +203,145 @@ class payment_order(orm.Model): cr, uid, ids, *args ) - """ - Hooks for processing direct debit orders, such as implemented in - account_direct_debit module. - """ - def debit_reconcile_transfer( - self, cr, uid, payment_order_id, amount, currency, context=None): + def debit_reconcile_transfer(self, cr, uid, payment_order_id, + amount, currency, context=None): """ - Reconcile the payment order if the amount is correct. Return the - id of the reconciliation. + During import of bank statements, create the reconcile on the transfer + account containing all the open move lines on the transfer account. """ - raise orm.except_orm( - _("Cannot reconcile"), - _("Cannot reconcile debit order: "+ - "Not implemented.")) + move_line_obj = self.pool.get('account.move.line') + order = self.browse(cr, uid, payment_order_id, context) + line_ids = [] + reconcile_id = False + for order_line in order.line_ids: + for line in order_line.debit_move_line_id.move_id.line_id: + if line.account_id.type == 'other' and not line.reconcile_id: + line_ids.append(line.id) + if self.pool.get('res.currency').is_zero( + cr, uid, currency, + move_line_obj.get_balance(cr, uid, line_ids) - amount): + reconcile_id = self.pool.get('account.move.reconcile').create( + cr, uid, + {'type': 'auto', 'line_id': [(6, 0, line_ids)]}, + context) + # set direct debit order to finished state + wf_service = netsvc.LocalService('workflow') + wf_service.trg_validate( + uid, 'payment.order', payment_order_id, 'done', cr) + return reconcile_id - def debit_unreconcile_transfer( - self, cr, uid, payment_order_id, reconcile_id, amount, currency, - context=None): - """ Unreconcile the payment_order if at all possible """ - raise orm.except_orm( - _("Cannot unreconcile"), - _("Cannot unreconcile debit order: "+ - "Not implemented.")) + def debit_unreconcile_transfer(self, cr, uid, payment_order_id, reconcile_id, + amount, currency, context=None): + """ + Due to a cancelled bank statements import, unreconcile the move on + the transfer account. Delegate the conditions to the workflow. + Raise on failure for rollback. + """ + self.pool.get('account.move.reconcile').unlink( + cr, uid, reconcile_id, context=context) + wkf_ok = netsvc.LocalService('workflow').trg_validate( + uid, 'payment.order', payment_order_id, 'undo_done', cr) + if not wkf_ok: + raise 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. + + The debit module actually enforces a criterium in its + override of this method for debit orders. + """ + return True + + def action_sent(self, cr, uid, ids, context=None): + """ + Create the moves that pay off the move lines from + the debit order. This happens when the debit order file is + generated. + """ + res = super(payment_order, self).action_sent( + cr, uid, ids, context) + + account_move_obj = self.pool.get('account.move') + account_move_line_obj = self.pool.get('account.move.line') + payment_line_obj = self.pool.get('payment.line') + for order in self.browse(cr, uid, ids, context=context): + for line in order.line_ids: + # basic checks + if not line.move_line_id: + raise orm.except_orm( + _('Error'), + _('No move line provided for line %s') % line.name) + 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 + ) + + move_id = account_move_obj.create(cr, uid, { + 'journal_id': order.mode.transfer_journal_id.id, + 'name': '%s order %s' % (order.payment_order_type, + line.move_line_id.move_id.name), + 'reference': '%s%s' % (order.payment_order_type[:3].upper(), + line.move_line_id.move_id.name), + }, context=context) + + # TODO: take multicurrency into account + + # create the debit move line on the transfer account + vals = { + 'name': '%s order for %s' % ( + order.payment_order_type, + line.move_line_id.invoice and + line.move_line_id.invoice.number or + line.move_line_id.name), + 'move_id': move_id, + 'partner_id': line.partner_id.id, + 'account_id': order.mode.transfer_account_id.id, + 'credit': (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), + } + transfer_move_line_id = account_move_line_obj.create( + cr, uid, vals, context=context) + + # create the debit move line on the receivable account + vals.update({ + 'account_id': line.move_line_id.account_id.id, + 'credit': (order.payment_order_type == 'debit' + and line.amount or 0.0), + 'debit': (order.payment_order_type == 'payment' + and line.amount or 0.0), + }) + reconcile_move_line_id = account_move_line_obj.create( + cr, uid, vals, context=context) + + # register the debit move line on the payment line + # and call reconciliation on it + payment_line_obj.write( + cr, uid, line.id, + {'debit_move_line_id': reconcile_move_line_id}, + context=context) + + payment_line_obj.debit_reconcile( + cr, uid, line.id, context=context) + account_move_obj.post(cr, uid, [move_id], context=context) + + self._write_payment_lines(cr, uid, ids, export_state='sent') + self.write(cr, uid, ids, { + 'state': 'sent', + 'date_sent': fields.date.context_today( + self, cr, uid, context=context), + }, context=context) + + return res diff --git a/account_banking_payment/model/payment_line.py b/account_banking_payment/model/payment_line.py index 30bef3564..7d1b29176 100644 --- a/account_banking_payment/model/payment_line.py +++ b/account_banking_payment/model/payment_line.py @@ -24,7 +24,8 @@ ############################################################################## from openerp.osv import orm, fields - +from openerp import netsvc +from openerp.tools.translate import _ class payment_line(orm.Model): ''' @@ -216,3 +217,86 @@ class payment_line(orm.Model): """ return False + + def debit_reconcile(self, cr, uid, payment_line_id, context=None): + """ + Reconcile a debit order's payment line with the the move line + that it is based on. Called from payment_order.action_sent(). + As the amount is derived directly from the counterpart move line, + we do not expect a write off. Take partially reconcilions into + account though. + + :param payment_line_id: the single id of the canceled payment line + """ + + if isinstance(payment_line_id, (list, tuple)): + payment_line_id = payment_line_id[0] + reconcile_obj = self.pool.get('account.move.reconcile') + move_line_obj = self.pool.get('account.move.line') + payment_line = self.browse(cr, uid, payment_line_id, context=context) + + debit_move_line = payment_line.debit_move_line_id + torec_move_line = payment_line.move_line_id + + if (not debit_move_line or not torec_move_line): + raise orm.except_orm( + _('Can not reconcile'), + _('No move line for line %s') % payment_line.name) + if torec_move_line.reconcile_id: # torec_move_line.reconcile_partial_id: + raise orm.except_orm( + _('Error'), + _('Move line %s has already been reconciled') % + torec_move_line.name + ) + if debit_move_line.reconcile_id or debit_move_line.reconcile_partial_id: + raise orm.except_orm( + _('Error'), + _('Move line %s has already been reconciled') % + debit_move_line.name + ) + + def is_zero(total): + return self.pool.get('res.currency').is_zero( + cr, uid, debit_move_line.company_id.currency_id, total) + + line_ids = [debit_move_line.id, torec_move_line.id] + if torec_move_line.reconcile_partial_id: + line_ids = [ + x.id for x in + debit_move_line.reconcile_partial_id.line_partial_ids + ] + [torec_move_line.id] + + total = move_line_obj.get_balance(cr, uid, line_ids) + vals = { + 'type': 'auto', + 'line_id': is_zero(total) and [(6, 0, line_ids)] or [(6, 0, [])], + 'line_partial_ids': is_zero(total) and [(6, 0, [])] or [(6, 0, line_ids)], + } + + if torec_move_line.reconcile_partial_id: + reconcile_obj.write( + cr, uid, debit_move_line.reconcile_partial_id.id, + vals, context=context) + else: + reconcile_obj.create( + cr, uid, vals, context=context) + for line_id in line_ids: + netsvc.LocalService("workflow").trg_trigger( + uid, 'account.move.line', line_id, cr) + + # If a bank transaction of a storno was first confirmed + # and now canceled (the invoice is now in state 'debit_denied' + if torec_move_line.invoice: + netsvc.LocalService("workflow").trg_validate( + uid, 'account.invoice', torec_move_line.invoice.id, + 'undo_debit_denied', cr) + + + _columns = { + 'debit_move_line_id': fields.many2one( + # this line is part of the credit side of move 2a + # from the documentation + 'account.move.line', 'Debit move line', + readonly=True, + help="Move line through which the debit order pays the invoice"), + } diff --git a/account_banking_payment/model/payment_mode.py b/account_banking_payment/model/payment_mode.py index a250afa40..9e2d30b27 100644 --- a/account_banking_payment/model/payment_mode.py +++ b/account_banking_payment/model/payment_mode.py @@ -48,4 +48,24 @@ class payment_mode(orm.Model): 'payment.mode.type', 'Payment type', help='Select the Payment Type for the Payment Mode.' ), + 'transfer_account_id': fields.many2one( + 'account.account', 'Transfer account', + domain=[('type', '=', 'other'), + ('reconcile', '=', True)], + help=('Pay off lines in sent orders with a ' + 'move on this account. For debit type modes only. ' + 'You can only select accounts of type regular that ' + 'are marked for reconciliation'), + ), + 'transfer_journal_id': fields.many2one( + 'account.journal', 'Transfer journal', + help=('Journal to write payment entries when confirming ' + 'a debit order of this mode'), + ), + 'payment_term_ids': fields.many2many( + 'account.payment.term', 'account_payment_order_terms_rel', + 'mode_id', 'term_id', 'Payment terms', + help=('Limit selected invoices to invoices with these payment ' + 'terms') + ), } diff --git a/account_direct_debit/model/__init__.py b/account_direct_debit/model/__init__.py index 4e258d292..b9f45c8e8 100644 --- a/account_direct_debit/model/__init__.py +++ b/account_direct_debit/model/__init__.py @@ -1,3 +1,4 @@ import account_payment +import payment_line import account_move_line import account_invoice diff --git a/account_direct_debit/model/account_payment.py b/account_direct_debit/model/account_payment.py index abdce4977..a36ac2ae4 100644 --- a/account_direct_debit/model/account_payment.py +++ b/account_direct_debit/model/account_payment.py @@ -1,36 +1,9 @@ # -*- coding: utf-8 -*- -import time -from osv import osv, fields +from openerp.osv import orm, fields import netsvc from tools.translate import _ -class payment_mode(osv.osv): - _inherit = 'payment.mode' - _columns = { - 'transfer_account_id': fields.many2one( - 'account.account', 'Transfer account', - domain=[('type', '=', 'other'), - ('reconcile', '=', True)], - help=('Pay off lines in sent orders with a ' + - 'move on this account. For debit type modes only. ' + - 'You can only select accounts of type regular that ' + - 'are marked for reconciliation'), - ), - 'transfer_journal_id': fields.many2one( - 'account.journal', 'Transfer journal', - help=('Journal to write payment entries when confirming ' + - 'a debit order of this mode'), - ), - 'payment_term_ids': fields.many2many( - 'account.payment.term', 'account_payment_order_terms_rel', - 'mode_id', 'term_id', 'Payment terms', - help=('Limit selected invoices to invoices with these payment ' + - 'terms') - ), - } -payment_mode() - -class payment_order(osv.osv): +class payment_order(orm.Model): _inherit = 'payment.order' def fields_view_get(self, cr, user, view_id=None, view_type='form', @@ -56,56 +29,11 @@ class payment_order(osv.osv): context['search_payment_order_type'])] # the magic is in the value of the selection res['fields']['mode']['selection'] = mode_obj._name_search( - cr, user, args=domain) + cr, user, args=domain, context=context) # also update the domain res['fields']['mode']['domain'] = domain return res - def debit_reconcile_transfer(self, cr, uid, payment_order_id, - amount, currency, context=None): - """ - During import of bank statements, create the reconcile on the transfer - account containing all the open move lines on the transfer account. - """ - move_line_obj = self.pool.get('account.move.line') - order = self.browse(cr, uid, payment_order_id, context) - line_ids = [] - reconcile_id = False - for order_line in order.line_ids: - for line in order_line.debit_move_line_id.move_id.line_id: - if line.account_id.type == 'other' and not line.reconcile_id: - line_ids.append(line.id) - if self.pool.get('res.currency').is_zero( - cr, uid, currency, - move_line_obj.get_balance(cr, uid, line_ids) - amount): - reconcile_id = self.pool.get('account.move.reconcile').create( - cr, uid, - {'type': 'auto', 'line_id': [(6, 0, line_ids)]}, - context) - # set direct debit order to finished state - wf_service = netsvc.LocalService('workflow') - wf_service.trg_validate( - uid, 'payment.order', payment_order_id, 'done', cr) - return reconcile_id - - def debit_unreconcile_transfer(self, cr, uid, payment_order_id, reconcile_id, - amount, currency, context=None): - """ - Due to a cancelled bank statements import, unreconcile the move on - the transfer account. Delegate the conditions to the workflow. - Raise on failure for rollback. - """ - self.pool.get('account.move.reconcile').unlink( - cr, uid, reconcile_id, context=context) - wkf_ok = netsvc.LocalService('workflow').trg_validate( - uid, 'payment.order', payment_order_id, 'undo_done', cr) - if not wkf_ok: - raise osv.except_osv( - _("Cannot unreconcile"), - _("Cannot unreconcile 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 @@ -117,367 +45,5 @@ class payment_order(osv.osv): for line in order.line_ids: if line.storno: return False - else: - # TODO: define conditions for 'payment' orders - return True - return True - - def action_sent(self, cr, uid, ids, context=None): - """ - Create the moves that pay off the move lines from - the debit order. This happens when the debit order file is - generated. - """ - res = super(payment_order, self).action_sent( - cr, uid, ids, context) - - account_move_obj = self.pool.get('account.move') - account_move_line_obj = self.pool.get('account.move.line') - payment_line_obj = self.pool.get('payment.line') - for order in self.browse(cr, uid, ids, context=context): - for line in order.line_ids: - # basic checks - if not line.move_line_id: - raise osv.except_osv( - _('Error'), - _('No move line provided for line %s') % line.name) - if line.move_line_id.reconcile_id: - raise osv.except_osv( - _('Error'), - _('Move line %s has already been paid/reconciled') % - line.move_line_id.name - ) - - move_id = account_move_obj.create(cr, uid, { - 'journal_id': order.mode.transfer_journal_id.id, - 'name': '%s order %s' % (order.payment_order_type, - line.move_line_id.move_id.name), - 'reference': '%s%s' % (order.payment_order_type[:3].upper(), - line.move_line_id.move_id.name), - }, context=context) - - # TODO: take multicurrency into account - - # create the debit move line on the transfer account - vals = { - 'name': '%s order for %s' % ( - order.payment_order_type, - line.move_line_id.invoice and - line.move_line_id.invoice.number or - line.move_line_id.name), - 'move_id': move_id, - 'partner_id': line.partner_id.id, - 'account_id': order.mode.transfer_account_id.id, - 'credit': (order.payment_order_type == 'payment' - and line.amount or 0.0), - 'debit': (order.payment_order_type == 'debit' - and line.amount or 0.0), - 'date': time.strftime('%Y-%m-%d'), - } - transfer_move_line_id = account_move_line_obj.create( - cr, uid, vals, context=context) - - # create the debit move line on the receivable account - vals.update({ - 'account_id': line.move_line_id.account_id.id, - 'credit': (order.payment_order_type == 'debit' - and line.amount or 0.0), - 'debit': (order.payment_order_type == 'payment' - and line.amount or 0.0), - }) - reconcile_move_line_id = account_move_line_obj.create( - cr, uid, vals, context=context) - - # register the debit move line on the payment line - # and call reconciliation on it - payment_line_obj.write( - cr, uid, line.id, - {'debit_move_line_id': reconcile_move_line_id}, - context=context) - - payment_line_obj.debit_reconcile( - cr, uid, line.id, context=context) - account_move_obj.post(cr, uid, [move_id], context=context) - return res - -payment_order() - -class payment_line(osv.osv): - _inherit = 'payment.line' - - def debit_storno(self, cr, uid, payment_line_id, amount, - currency, storno_retry=True, context=None): - """ - The processing of a storno is triggered by a debit - transfer on one of the company's bank accounts. - This method offers to re-reconcile the original debit - payment. For this purpose, we have registered that - payment move on the payment line. - - Return the (now incomplete) reconcile id. The caller MUST - re-reconcile this reconcile with the bank transfer and - re-open the associated invoice. - - :param payment_line_id: the single payment line id - :param amount: the (signed) amount debited from the bank account - :param currency: the bank account's currency *browse object* - :param boolean storno_retry: when True, attempt to reopen the invoice, \ - set the invoice to 'Debit denied' otherwise. - :return: an incomplete reconcile for the caller to fill - :rtype: database id of an account.move.reconcile resource. - """ - - move_line_obj = self.pool.get('account.move.line') - reconcile_obj = self.pool.get('account.move.reconcile') - line = self.browse(cr, uid, payment_line_id) - reconcile_id = False - if (line.debit_move_line_id and not line.storno and - self.pool.get('res.currency').is_zero( - cr, uid, currency, ( - (line.debit_move_line_id.credit or 0.0) - - (line.debit_move_line_id.debit or 0.0) + amount))): - # Two different cases, full and partial - # Both cases differ subtly in the procedure to follow - # Needs refractoring, but why is this not in the OpenERP API? - # Actually, given the nature of a direct debit order and storno, - # we should not need to take partial into account on the side of - # the debit_move_line. - if line.debit_move_line_id.reconcile_partial_id: - reconcile_id = line.debit_move_line_id.reconcile_partial_id.id - attribute = 'reconcile_partial_id' - if len(line.debit_move_line_id.reconcile_id.line_partial_ids) == 2: - # reuse the simple reconcile for the storno transfer - reconcile_obj.write( - cr, uid, reconcile_id, { - 'line_id': [(6, 0, line.debit_move_line_id.id)], - 'line_partial_ids': [(6, 0, [])], - }, context=context) - else: - # split up the original reconcile in a partial one - # and a new one for reconciling the storno transfer - reconcile_obj.write( - cr, uid, reconcile_id, { - 'line_partial_ids': [(3, line.debit_move_line_id.id)], - }, context=context) - reconcile_id = reconcile_obj.create( - cr, uid, { - 'type': 'auto', - 'line_id': [(6, 0, line.debit_move_line_id.id)], - }, context=context) - elif line.debit_move_line_id.reconcile_id: - reconcile_id = line.debit_move_line_id.reconcile_id.id - if len(line.debit_move_line_id.reconcile_id.line_id) == 2: - # reuse the simple reconcile for the storno transfer - reconcile_obj.write( - cr, uid, reconcile_id, { - 'line_id': [(6, 0, [line.debit_move_line_id.id])] - }, context=context) - else: - # split up the original reconcile in a partial one - # and a new one for reconciling the storno transfer - partial_ids = [ - x.id for x in line.debit_move_line_id.reconcile_id.line_id - if x.id != line.debit_move_line_id.id - ] - reconcile_obj.write( - cr, uid, reconcile_id, { - 'line_partial_ids': [(6, 0, partial_ids)], - 'line_id': [(6, 0, [])], - }, context=context) - reconcile_id = reconcile_obj.create( - cr, uid, { - 'type': 'auto', - 'line_id': [(6, 0, line.debit_move_line_id.id)], - }, context=context) - # mark the payment line for storno processed - if reconcile_id: - self.write(cr, uid, [payment_line_id], - {'storno': True}, context=context) - # put forth the invoice workflow - if line.move_line_id.invoice: - activity = (storno_retry and 'open_test' - or 'invoice_debit_denied') - netsvc.LocalService("workflow").trg_validate( - uid, 'account.invoice', line.move_line_id.invoice.id, - activity, cr) - return reconcile_id - - def get_storno_account_id(self, cr, uid, payment_line_id, amount, - currency, context=None): - """ - Check the match of the arguments, and return the account associated - with the storno. - Used in account_banking interactive mode - - :param payment_line_id: the single payment line id - :param amount: the (signed) amount debited from the bank account - :param currency: the bank account's currency *browse object* - :return: an account if there is a full match, False otherwise - :rtype: database id of an account.account resource. - """ - - line = self.browse(cr, uid, payment_line_id) - account_id = False - if (line.debit_move_line_id and not line.storno and - self.pool.get('res.currency').is_zero( - cr, uid, currency, ( - (line.debit_move_line_id.credit or 0.0) - - (line.debit_move_line_id.debit or 0.0) + amount))): - account_id = line.debit_move_line_id.account_id.id - return account_id - - def debit_reconcile(self, cr, uid, payment_line_id, context=None): - """ - Reconcile a debit order's payment line with the the move line - that it is based on. Called from payment_order.action_sent(). - As the amount is derived directly from the counterpart move line, - we do not expect a write off. Take partially reconcilions into - account though. - - :param payment_line_id: the single id of the canceled payment line - """ - - if isinstance(payment_line_id, (list, tuple)): - payment_line_id = payment_line_id[0] - reconcile_obj = self.pool.get('account.move.reconcile') - move_line_obj = self.pool.get('account.move.line') - payment_line = self.browse(cr, uid, payment_line_id, context=context) - - debit_move_line = payment_line.debit_move_line_id - torec_move_line = payment_line.move_line_id - - if payment_line.storno: - raise osv.except_osv( - _('Can not reconcile'), - _('Cancelation of payment line \'%s\' has already been ' + - 'processed') % payment_line.name) - if (not debit_move_line or not torec_move_line): - raise osv.except_osv( - _('Can not reconcile'), - _('No move line for line %s') % payment_line.name) - if torec_move_line.reconcile_id: # torec_move_line.reconcile_partial_id: - raise osv.except_osv( - _('Error'), - _('Move line %s has already been reconciled') % - torec_move_line.name - ) - if debit_move_line.reconcile_id or debit_move_line.reconcile_partial_id: - raise osv.except_osv( - _('Error'), - _('Move line %s has already been reconciled') % - debit_move_line.name - ) - - def is_zero(total): - return self.pool.get('res.currency').is_zero( - cr, uid, debit_move_line.company_id.currency_id, total) - - line_ids = [debit_move_line.id, torec_move_line.id] - if torec_move_line.reconcile_partial_id: - line_ids = [ - x.id for x in debit_move_line.reconcile_partial_id.line_partial_ids] + [torec_move_line_id] - - total = move_line_obj.get_balance(cr, uid, line_ids) - vals = { - 'type': 'auto', - 'line_id': is_zero(total) and [(6, 0, line_ids)] or [(6, 0, [])], - 'line_partial_ids': is_zero(total) and [(6, 0, [])] or [(6, 0, line_ids)], - } - - if torec_move_line.reconcile_partial_id: - reconcile_obj.write( - cr, uid, debit_move_line.reconcile_partial_id.id, - vals, context=context) - else: - reconcile_obj.create( - cr, uid, vals, context=context) - for line_id in line_ids: - netsvc.LocalService("workflow").trg_trigger( - uid, 'account.move.line', line_id, cr) - - # If a bank transaction of a storno was first confirmed - # and now canceled (the invoice is now in state 'debit_denied' - if torec_move_line.invoice: - netsvc.LocalService("workflow").trg_validate( - uid, 'account.invoice', torec_move_line.invoice.id, - 'undo_debit_denied', cr) - - - - _columns = { - 'debit_move_line_id': fields.many2one( - # this line is part of the credit side of move 2a - # from the documentation - 'account.move.line', 'Debit move line', - readonly=True, - help="Move line through which the debit order pays the invoice"), - 'storno': fields.boolean( - 'Storno', - readonly=True, - help=("If this is true, the debit order has been canceled " + - "by the bank or by the customer")), - } -payment_line() - - -class payment_order_create(osv.osv_memory): - _inherit = 'payment.order.create' - - def search_entries(self, cr, uid, ids, context=None): - """ - This method taken from account_payment module. - We adapt the domain based on the payment_order_type - """ - line_obj = self.pool.get('account.move.line') - mod_obj = self.pool.get('ir.model.data') - if context is None: - context = {} - data = self.read(cr, uid, ids, [], context=context)[0] - search_due_date = data['duedate'] - - ### start account_direct_debit ### - payment = self.pool.get('payment.order').browse( - cr, uid, context['active_id'], context=context) - # Search for move line to pay: - if payment.payment_order_type == 'debit': - domain = [ - ('reconcile_id', '=', False), - ('account_id.type', '=', 'receivable'), - ('invoice.state', '!=', 'debit_denied'), - ('amount_to_receive', '>', 0), - ] - else: - domain = [ - ('reconcile_id', '=', False), - ('account_id.type', '=', 'payable'), - ('amount_to_pay', '>', 0) - ] - domain.append(('company_id', '=', payment.mode.company_id.id)) - # apply payment term filter - if payment.mode.payment_term_ids: - domain = domain + [ - ('invoice.payment_term', 'in', - [term.id for term in payment.mode.payment_term_ids] - ) - ] - # domain = [('reconcile_id', '=', False), ('account_id.type', '=', 'payable'), ('amount_to_pay', '>', 0)] - ### end account_direct_debit ### - - domain = domain + ['|', ('date_maturity', '<=', search_due_date), ('date_maturity', '=', False)] - line_ids = line_obj.search(cr, uid, domain, context=context) - context.update({'line_ids': line_ids}) - model_data_ids = mod_obj.search(cr, uid,[('model', '=', 'ir.ui.view'), ('name', '=', 'view_create_payment_order_lines')], context=context) - resource_id = mod_obj.read(cr, uid, model_data_ids, fields=['res_id'], context=context)[0]['res_id'] - return {'name': ('Entry Lines'), - 'context': context, - 'view_type': 'form', - 'view_mode': 'form', - 'res_model': 'payment.order.create', - 'views': [(resource_id,'form')], - 'type': 'ir.actions.act_window', - 'target': 'new', - } -payment_order_create() - - - + return super(payment_order, self).test_undo_done( + cr, uid, ids, context=context) diff --git a/account_direct_debit/model/payment_line.py b/account_direct_debit/model/payment_line.py new file mode 100644 index 000000000..7bfbe8527 --- /dev/null +++ b/account_direct_debit/model/payment_line.py @@ -0,0 +1,211 @@ +# -*- coding: utf-8 -*- +from openerp.osv import orm, fields +import netsvc +from tools.translate import _ + +class payment_line(orm.Model): + _inherit = 'payment.line' + + def debit_storno(self, cr, uid, payment_line_id, amount, + currency, storno_retry=True, context=None): + """ + The processing of a storno is triggered by a debit + transfer on one of the company's bank accounts. + This method offers to re-reconcile the original debit + payment. For this purpose, we have registered that + payment move on the payment line. + + Return the (now incomplete) reconcile id. The caller MUST + re-reconcile this reconcile with the bank transfer and + re-open the associated invoice. + + :param payment_line_id: the single payment line id + :param amount: the (signed) amount debited from the bank account + :param currency: the bank account's currency *browse object* + :param boolean storno_retry: when True, attempt to reopen the invoice, \ + set the invoice to 'Debit denied' otherwise. + :return: an incomplete reconcile for the caller to fill + :rtype: database id of an account.move.reconcile resource. + """ + + move_line_obj = self.pool.get('account.move.line') + reconcile_obj = self.pool.get('account.move.reconcile') + line = self.browse(cr, uid, payment_line_id) + reconcile_id = False + if (line.debit_move_line_id and not line.storno and + self.pool.get('res.currency').is_zero( + cr, uid, currency, ( + (line.debit_move_line_id.credit or 0.0) - + (line.debit_move_line_id.debit or 0.0) + amount))): + # Two different cases, full and partial + # Both cases differ subtly in the procedure to follow + # Needs refractoring, but why is this not in the OpenERP API? + # Actually, given the nature of a direct debit order and storno, + # we should not need to take partial into account on the side of + # the debit_move_line. + if line.debit_move_line_id.reconcile_partial_id: + reconcile_id = line.debit_move_line_id.reconcile_partial_id.id + attribute = 'reconcile_partial_id' + if len(line.debit_move_line_id.reconcile_id.line_partial_ids) == 2: + # reuse the simple reconcile for the storno transfer + reconcile_obj.write( + cr, uid, reconcile_id, { + 'line_id': [(6, 0, line.debit_move_line_id.id)], + 'line_partial_ids': [(6, 0, [])], + }, context=context) + else: + # split up the original reconcile in a partial one + # and a new one for reconciling the storno transfer + reconcile_obj.write( + cr, uid, reconcile_id, { + 'line_partial_ids': [(3, line.debit_move_line_id.id)], + }, context=context) + reconcile_id = reconcile_obj.create( + cr, uid, { + 'type': 'auto', + 'line_id': [(6, 0, line.debit_move_line_id.id)], + }, context=context) + elif line.debit_move_line_id.reconcile_id: + reconcile_id = line.debit_move_line_id.reconcile_id.id + if len(line.debit_move_line_id.reconcile_id.line_id) == 2: + # reuse the simple reconcile for the storno transfer + reconcile_obj.write( + cr, uid, reconcile_id, { + 'line_id': [(6, 0, [line.debit_move_line_id.id])] + }, context=context) + else: + # split up the original reconcile in a partial one + # and a new one for reconciling the storno transfer + partial_ids = [ + x.id for x in line.debit_move_line_id.reconcile_id.line_id + if x.id != line.debit_move_line_id.id + ] + reconcile_obj.write( + cr, uid, reconcile_id, { + 'line_partial_ids': [(6, 0, partial_ids)], + 'line_id': [(6, 0, [])], + }, context=context) + reconcile_id = reconcile_obj.create( + cr, uid, { + 'type': 'auto', + 'line_id': [(6, 0, line.debit_move_line_id.id)], + }, context=context) + # mark the payment line for storno processed + if reconcile_id: + self.write(cr, uid, [payment_line_id], + {'storno': True}, context=context) + # put forth the invoice workflow + if line.move_line_id.invoice: + activity = (storno_retry and 'open_test' + or 'invoice_debit_denied') + netsvc.LocalService("workflow").trg_validate( + uid, 'account.invoice', line.move_line_id.invoice.id, + activity, cr) + return reconcile_id + + def get_storno_account_id(self, cr, uid, payment_line_id, amount, + currency, context=None): + """ + Check the match of the arguments, and return the account associated + with the storno. + Used in account_banking interactive mode + + :param payment_line_id: the single payment line id + :param amount: the (signed) amount debited from the bank account + :param currency: the bank account's currency *browse object* + :return: an account if there is a full match, False otherwise + :rtype: database id of an account.account resource. + """ + + line = self.browse(cr, uid, payment_line_id) + account_id = False + if (line.debit_move_line_id and not line.storno and + self.pool.get('res.currency').is_zero( + cr, uid, currency, ( + (line.debit_move_line_id.credit or 0.0) - + (line.debit_move_line_id.debit or 0.0) + amount))): + account_id = line.debit_move_line_id.account_id.id + return account_id + + def debit_reconcile(self, cr, uid, payment_line_id, context=None): + """ + Raise if a payment line is passed for which storno is True + """ + if isinstance(payment_line_id, (list, tuple)): + payment_line_id = payment_line_id[0] + payment_line = self.read( + cr, uid, payment_line_id, ['storno', 'name'], context=context) + if payment_line['storno']: + raise orm.except_orm( + _('Can not reconcile'), + _('Cancelation of payment line \'%s\' has already been ' + 'processed') % payment_line['name']) + return super(self, payment_line).debit_reconcile( + cr, uid, payment_line_id, context=context) + + _columns = { + 'storno': fields.boolean( + 'Storno', + readonly=True, + help=("If this is true, the debit order has been canceled " + "by the bank or by the customer")), + } + + +class payment_order_create(orm.Model_memory): + _inherit = 'payment.order.create' + + def search_entries(self, cr, uid, ids, context=None): + """ + This method taken from account_payment module. + We adapt the domain based on the payment_order_type + """ + line_obj = self.pool.get('account.move.line') + mod_obj = self.pool.get('ir.model.data') + if context is None: + context = {} + data = self.read(cr, uid, ids, [], context=context)[0] + search_due_date = data['duedate'] + + ### start account_direct_debit ### + payment = self.pool.get('payment.order').browse( + cr, uid, context['active_id'], context=context) + # Search for move line to pay: + if payment.payment_order_type == 'debit': + domain = [ + ('reconcile_id', '=', False), + ('account_id.type', '=', 'receivable'), + ('invoice.state', '!=', 'debit_denied'), + ('amount_to_receive', '>', 0), + ] + else: + domain = [ + ('reconcile_id', '=', False), + ('account_id.type', '=', 'payable'), + ('amount_to_pay', '>', 0) + ] + domain.append(('company_id', '=', payment.mode.company_id.id)) + # apply payment term filter + if payment.mode.payment_term_ids: + domain = domain + [ + ('invoice.payment_term', 'in', + [term.id for term in payment.mode.payment_term_ids] + ) + ] + # domain = [('reconcile_id', '=', False), ('account_id.type', '=', 'payable'), ('amount_to_pay', '>', 0)] + ### end account_direct_debit ### + + domain = domain + ['|', ('date_maturity', '<=', search_due_date), ('date_maturity', '=', False)] + line_ids = line_obj.search(cr, uid, domain, context=context) + context.update({'line_ids': line_ids}) + model_data_ids = mod_obj.search(cr, uid,[('model', '=', 'ir.ui.view'), ('name', '=', 'view_create_payment_order_lines')], context=context) + resource_id = mod_obj.read(cr, uid, model_data_ids, fields=['res_id'], context=context)[0]['res_id'] + return {'name': ('Entry Lines'), + 'context': context, + 'view_type': 'form', + 'view_mode': 'form', + 'res_model': 'payment.order.create', + 'views': [(resource_id,'form')], + 'type': 'ir.actions.act_window', + 'target': 'new', + }