mirror of
https://github.com/OCA/bank-payment.git
synced 2025-02-02 10:37:31 +02:00
[RFR] Sort out payment/payment line code between payment and debit module
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"),
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
),
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import account_payment
|
||||
import payment_line
|
||||
import account_move_line
|
||||
import account_invoice
|
||||
|
||||
@@ -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)
|
||||
|
||||
211
account_direct_debit/model/payment_line.py
Normal file
211
account_direct_debit/model/payment_line.py
Normal file
@@ -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',
|
||||
}
|
||||
Reference in New Issue
Block a user