From 5c69350246a1d09359df9e413cff6f181d74a9c0 Mon Sep 17 00:00:00 2001 From: OpenERP instance user Date: Sun, 11 Dec 2011 16:00:41 +0100 Subject: [PATCH] [RFR] debit reconciliation [ADD] storno processing --- account_direct_debit/__init__.py | 4 +- account_direct_debit/model/__init__.py | 3 + .../{ => model}/account_invoice.py | 0 .../{ => model}/account_move_line.py | 0 .../{ => model}/account_payment.py | 241 +++++++++++++----- 5 files changed, 186 insertions(+), 62 deletions(-) create mode 100644 account_direct_debit/model/__init__.py rename account_direct_debit/{ => model}/account_invoice.py (100%) rename account_direct_debit/{ => model}/account_move_line.py (100%) rename account_direct_debit/{ => model}/account_payment.py (55%) diff --git a/account_direct_debit/__init__.py b/account_direct_debit/__init__.py index 4e258d292..16e8b082f 100644 --- a/account_direct_debit/__init__.py +++ b/account_direct_debit/__init__.py @@ -1,3 +1 @@ -import account_payment -import account_move_line -import account_invoice +import model diff --git a/account_direct_debit/model/__init__.py b/account_direct_debit/model/__init__.py new file mode 100644 index 000000000..4e258d292 --- /dev/null +++ b/account_direct_debit/model/__init__.py @@ -0,0 +1,3 @@ +import account_payment +import account_move_line +import account_invoice diff --git a/account_direct_debit/account_invoice.py b/account_direct_debit/model/account_invoice.py similarity index 100% rename from account_direct_debit/account_invoice.py rename to account_direct_debit/model/account_invoice.py diff --git a/account_direct_debit/account_move_line.py b/account_direct_debit/model/account_move_line.py similarity index 100% rename from account_direct_debit/account_move_line.py rename to account_direct_debit/model/account_move_line.py diff --git a/account_direct_debit/account_payment.py b/account_direct_debit/model/account_payment.py similarity index 55% rename from account_direct_debit/account_payment.py rename to account_direct_debit/model/account_payment.py index 1603c2ee8..b41bcde05 100644 --- a/account_direct_debit/account_payment.py +++ b/account_direct_debit/model/account_payment.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import time from osv import osv, fields import netsvc from tools.translate import _ @@ -49,47 +50,6 @@ class payment_order(osv.osv): res['fields']['mode']['domain'] = domain return res - def _reconcile_debit_order_move_line(self, cr, uid, origin_move_line_id, - transfer_move_line_id, context=None): - """ - Reconcile the debit order's move lines at generation time. - As the amount is derived directly from the counterpart move line, - we do not expect a write off. Take partially reconcilions into - account though. - """ - reconcile_obj = self.pool.get('account.move.reconcile') - move_line_obj = self.pool.get('account.move.line') - line_ids = [origin_move_line_id, transfer_move_line_id] - (origin, transfer) = move_line_obj.browse( - cr, uid, line_ids, context=context) - if origin.reconcile_partial_id: - line_ids = [x.id for x in - origin.reconcile_partial_id.line_partial_ids + [transfer] - ] - - total = 0.0 - company_currency_id = origin.company_id.currency_id - for line in move_line_obj.read( - cr, uid, line_ids, ['debit', 'credit'], context=context): - total += (line['debit'] or 0.0) - (line['credit'] or 0.0) - full = self.pool.get('res.currency').is_zero( - cr, uid, company_currency_id, total) - vals = { - 'type': 'auto', - 'line_id': full and [(6, 0, line_ids)] or [(6, 0, [])], - 'line_partial_ids': full and [(6, 0, [])] or [(6, 0, line_ids)], - } - if origin.reconcile_partial_id: - reconcile_obj.write( - cr, uid, origin.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) - def action_sent(self, cr, uid, ids, context=None): """ Create the moves that pay off the move lines from @@ -137,6 +97,7 @@ class payment_order(osv.osv): 'account_id': order.mode.transfer_account_id.id, 'credit': 0.0, 'debit': line.amount, + 'date': time.strftime('%Y-%m-%d'), } transfer_move_line_id = account_move_line_obj.create( cr, uid, vals, context=context) @@ -149,20 +110,196 @@ class payment_order(osv.osv): }) 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._reconcile_debit_order_move_line( - cr, uid, line.move_line_id.id, reconcile_move_line_id, - context=context) return res payment_order() +class payment_line(osv.osv): + _inherit = 'payment.line' + + def debit_storno(self, cr, uid, payment_line_id, storno_move_line_id, context=None): + """ + Process a payment line from a direct debit order which has + been canceled by the bank or by the user: + - Undo the reconciliation of the payment line with the move + line that it originated from, and re-reconciliated with + the credit payment in the bank journal of the same amount and + on the same account. + - Mark the payment line for being reversed. + + :param payment_line_id: the single id of the canceled payment line + :param storno_move_line_id: the credit payment in the bank journal + """ + + 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 + if (not debit_move_line): + raise osv.except_osv( + _('Can not process storno'), + _('No move line for line %s') % payment_line.name) + if payment_line.storno: + raise osv.except_osv( + _('Can not process storno'), + _('Cancelation of payment line \'%s\' has already been ' + + 'processed') % payment_line.name) + + def is_zero(total): + return self.pool.get('res.currency').is_zero( + cr, uid, debit_move_line.company_id.currency_id, total) + + # check validity of the proposed move line + torec_move_line = move_line_obj.browse( + cr, uid, storno_move_line_id, context=context) + if not (is_zero(torec_move_line.debit - debit_move_line.debit) and + is_zero(torec_move_line.credit - debit_move_line.credit) and + torec_move_line.account_id.id == debit_move_line.account_id.id): + raise osv.except_osv( + _('Can not process storno'), + _('%s is not a drop-in replacement for %s') % ( + torec_move_line.name, debit_move_line.name)) + if payment_line.storno: + raise osv.except_osv( + _('Can not process storno'), + _('Debit order line %s has already been cancelled') % ( + payment_line.name)) + + # replace move line in reconciliation + reconcile_id = False + if (payment_line.move_line_id.reconcile_partial_id and + debit_move_line_id.id in + payment_line.move_line_id.reconcile_partial_id.line_partial_ids): + reconcile_id = payment_line.move_line_id.reconcile_partial_id + vals = { + 'line_partial_ids': + [(3, debit_move_line_id.id), (4, torec_move_line.id)], + } + elif (payment_line.move_line_id.reconcile_id and + debit_move_line_id.id in + payment_line.move_line_id.reconcile_id.line_id): + reconcile_id = payment_line.move_line_id.reconcile_id + vals = { + 'line_id': + [(3, debit_move_line_id.id), (4, torec_move_line.id)] + } + if not reconcile_id: + raise osv.except_osv( + _('Can not perform storno'), + _('Debit order line %s does not occur in the list of ' + 'reconciliation move lines of its origin') % + debit_move_line_id.name) + reconcile_obj.write(cr, uid, reconcile_id, vals, context=context) + self.write(cr, uid, payment_line_id, {'storno': True}, context=context) + #for line_id in line_ids: + # netsvc.LocalService("workflow").trg_trigger( + # uid, 'account.move.line', line_id, cr) + + 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. + 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 or torec_move_line.reconcile_partial_id: + raise osv.except_osv( + _('Error'), + _('Move line %s has already been reconciled') % + torec_move_line.name + ) + if torec_move_line.reconcile_id or torec_move_line.reconcile_partial_id: + raise osv.except_osv( + _('Error'), + _('Move line %s has already been reconciled') % + torec_move_line.name + ) + + def get_balance(line_ids): + total = 0.0 + for line in move_line_obj.read( + cr, uid, line_ids, ['debit', 'credit'], context=context): + total += (line['debit'] or 0.0) - (line['credit'] or 0.0) + return total + + 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 debit_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 = get_balance(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 debit_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) + + _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' @@ -204,19 +341,5 @@ class payment_order_create(osv.osv_memory): } payment_order_create() -class payment_line(osv.osv): - _inherit = 'payment.line' - _columns = { - 'debit_move_line_id': fields.many2one( - '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()