mirror of
https://github.com/OCA/bank-payment.git
synced 2025-02-02 10:37:31 +02:00
Merge pull request #197 from hbrunn/8.0-payment-transfer-std-reconcile-method-ape-v8_api
8.0 payment transfer std reconcile method ape v8 api
This commit is contained in:
@@ -87,7 +87,8 @@ class PaymentOrder(models.Model):
|
||||
.Integer(string='Partial Reconciles Counter',
|
||||
compute='get_partial_reconcile_count')
|
||||
|
||||
def action_rejected(self, cr, uid, ids, context=None):
|
||||
@api.multi
|
||||
def action_rejected(self):
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
@@ -129,13 +130,8 @@ class PaymentOrder(models.Model):
|
||||
def test_undo_done(self):
|
||||
return not self.test_done()
|
||||
|
||||
@api.model
|
||||
@api.multi
|
||||
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' % (
|
||||
@@ -143,7 +139,7 @@ class PaymentOrder(models.Model):
|
||||
}
|
||||
return vals
|
||||
|
||||
@api.model
|
||||
@api.multi
|
||||
def _prepare_move_line_transfer_account(
|
||||
self, amount, move, payment_lines, labels):
|
||||
if len(payment_lines) == 1:
|
||||
@@ -175,7 +171,7 @@ class PaymentOrder(models.Model):
|
||||
}
|
||||
return vals
|
||||
|
||||
@api.model
|
||||
@api.multi
|
||||
def _prepare_move_line_partner_account(self, line, move, labels):
|
||||
if line.move_line_id:
|
||||
account_id = line.move_line_id.account_id.id
|
||||
@@ -197,11 +193,35 @@ class PaymentOrder(models.Model):
|
||||
}
|
||||
return vals
|
||||
|
||||
@api.model
|
||||
@api.multi
|
||||
def action_sent_no_move_line_hook(self, pay_line):
|
||||
"""This function is designed to be inherited"""
|
||||
return
|
||||
|
||||
@api.multi
|
||||
def _create_move_line_partner_account(self, line, move, labels):
|
||||
"""This method is designed to be inherited in a custom module"""
|
||||
|
||||
# TODO: take multicurrency into account
|
||||
aml_obj = self.env['account.move.line']
|
||||
# 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)
|
||||
|
||||
# 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})
|
||||
|
||||
@api.multi
|
||||
def _reconcile_payment_lines(self, payment_lines):
|
||||
for line in payment_lines:
|
||||
if line.move_line_id:
|
||||
line.debit_reconcile()
|
||||
else:
|
||||
self.action_sent_no_move_line_hook(line)
|
||||
|
||||
@api.one
|
||||
def action_sent(self):
|
||||
"""
|
||||
@@ -211,7 +231,6 @@ class PaymentOrder(models.Model):
|
||||
"""
|
||||
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'),
|
||||
@@ -242,28 +261,13 @@ class PaymentOrder(models.Model):
|
||||
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)
|
||||
|
||||
self._create_move_line_partner_account(line, move, labels)
|
||||
# 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)
|
||||
self._reconcile_payment_lines(lines)
|
||||
|
||||
# consider entry_posted on account_journal
|
||||
if move.journal_id.entry_posted:
|
||||
|
||||
@@ -22,13 +22,11 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import orm, fields
|
||||
from openerp import workflow
|
||||
from openerp import models, fields, workflow, api, exceptions
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
||||
class PaymentLine(orm.Model):
|
||||
class PaymentLine(models.Model):
|
||||
'''
|
||||
Add some fields; make destination bank account
|
||||
mandatory, as it makes no sense to send payments into thin air.
|
||||
@@ -37,51 +35,35 @@ class PaymentLine(orm.Model):
|
||||
'''
|
||||
_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):
|
||||
@api.multi
|
||||
def _get_transfer_move_line(self):
|
||||
for order_line in self:
|
||||
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
|
||||
order_line.transfer_move_line_id = move_line
|
||||
elif order_type == 'payment' and move_line.credit > 0:
|
||||
res[order_line.id] = move_line.id
|
||||
else:
|
||||
res[order_line.id] = False
|
||||
return res
|
||||
order_line.transfer_move_line_id = move_line
|
||||
|
||||
_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(
|
||||
'account.move.line', 'Transfer move line',
|
||||
readonly=True,
|
||||
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",
|
||||
),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'msg': '',
|
||||
}
|
||||
msg = fields.Char('Message', required=False, readonly=True, default='')
|
||||
date_done = fields.Date('Date Confirmed', select=True, readonly=True)
|
||||
transit_move_line_id = fields.Many2one(
|
||||
'account.move.line', string='Transfer move line', readonly=True,
|
||||
help="Move line through which the payment/debit order "
|
||||
"pays the invoice")
|
||||
transfer_move_line_id = fields.Many2one(
|
||||
'account.move.line', compute='_get_transfer_move_line',
|
||||
string='Transfer move line counterpart',
|
||||
help="Counterpart move line on the transfer account")
|
||||
|
||||
"""
|
||||
Hooks for processing direct debit orders, such as implemented in
|
||||
account_direct_debit module.
|
||||
"""
|
||||
def get_storno_account_id(self, cr, uid, payment_line_id, amount,
|
||||
currency_id, context=None):
|
||||
@api.multi
|
||||
def get_storno_account_id(self, amount, currency_id):
|
||||
"""
|
||||
Hook for verifying a match of the payment line with the amount.
|
||||
Return the account associated with the storno.
|
||||
@@ -95,8 +77,8 @@ class PaymentLine(orm.Model):
|
||||
|
||||
return False
|
||||
|
||||
def debit_storno(self, cr, uid, payment_line_id, amount,
|
||||
currency_id, storno_retry=True, context=None):
|
||||
@api.multi
|
||||
def debit_storno(self, amount, currency_id, storno_retry=True):
|
||||
"""
|
||||
Hook for handling a canceled item of a direct debit order.
|
||||
Presumably called from a bank statement import routine.
|
||||
@@ -116,7 +98,8 @@ class PaymentLine(orm.Model):
|
||||
|
||||
return False
|
||||
|
||||
def debit_reconcile(self, cr, uid, payment_line_id, context=None):
|
||||
@api.one
|
||||
def debit_reconcile(self):
|
||||
"""
|
||||
Reconcile a debit order's payment line with the the move line
|
||||
that it is based on. Called from payment_order.action_sent().
|
||||
@@ -127,68 +110,35 @@ class PaymentLine(orm.Model):
|
||||
: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)
|
||||
|
||||
transit_move_line = payment_line.transit_move_line_id
|
||||
torec_move_line = payment_line.move_line_id
|
||||
transit_move_line = self.transit_move_line_id
|
||||
torec_move_line = self.move_line_id
|
||||
|
||||
if (not transit_move_line or not torec_move_line):
|
||||
raise orm.except_orm(
|
||||
raise exceptions.except_orm(
|
||||
_('Can not reconcile'),
|
||||
_('No move line for line %s') % payment_line.name
|
||||
_('No move line for line %s') % self.name
|
||||
)
|
||||
if torec_move_line.reconcile_id:
|
||||
raise orm.except_orm(
|
||||
raise exceptions.except_orm(
|
||||
_('Error'),
|
||||
_('Move line %s has already been reconciled') %
|
||||
torec_move_line.name
|
||||
)
|
||||
if (transit_move_line.reconcile_id or
|
||||
transit_move_line.reconcile_partial_id):
|
||||
raise orm.except_orm(
|
||||
raise exceptions.except_orm(
|
||||
_('Error'),
|
||||
_('Move line %s has already been reconciled') %
|
||||
transit_move_line.name
|
||||
)
|
||||
|
||||
def is_zero(total):
|
||||
return self.pool.get('res.currency').is_zero(
|
||||
cr, uid, transit_move_line.company_id.currency_id, total)
|
||||
|
||||
line_ids = [transit_move_line.id, torec_move_line.id]
|
||||
if torec_move_line.reconcile_partial_id:
|
||||
line_ids = [
|
||||
x.id for x in
|
||||
torec_move_line.reconcile_partial_id.line_partial_ids
|
||||
] + [transit_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, [torec_move_line.reconcile_partial_id.id],
|
||||
vals, context=context)
|
||||
else:
|
||||
reconcile_obj.create(
|
||||
cr, uid, vals, context=context)
|
||||
for line_id in line_ids:
|
||||
workflow.trg_trigger(
|
||||
uid, 'account.move.line', line_id, cr)
|
||||
self.env['account.move.line'].browse(line_ids).reconcile_partial(
|
||||
type='auto')
|
||||
|
||||
# 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:
|
||||
workflow.trg_validate(
|
||||
uid, 'account.invoice', torec_move_line.invoice.id,
|
||||
'undo_debit_denied', cr)
|
||||
self.env.uid, 'account.invoice', torec_move_line.invoice.id,
|
||||
'undo_debit_denied', self.env.cr)
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from openerp.osv import orm, fields
|
||||
from openerp import netsvc
|
||||
from openerp.tools.translate import _
|
||||
from openerp import api, exceptions, models, fields, _, workflow
|
||||
|
||||
|
||||
class PaymentLine(orm.Model):
|
||||
class PaymentLine(models.Model):
|
||||
_inherit = 'payment.line'
|
||||
|
||||
def debit_storno(self, cr, uid, payment_line_id, amount,
|
||||
currency, storno_retry=True, context=None):
|
||||
@api.multi
|
||||
def debit_storno(self, amount, currency, storno_retry=True):
|
||||
"""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
|
||||
@@ -27,12 +25,13 @@ class PaymentLine(orm.Model):
|
||||
:return: an incomplete reconcile for the caller to fill
|
||||
:rtype: database id of an account.move.reconcile resource.
|
||||
"""
|
||||
reconcile_obj = self.pool.get('account.move.reconcile')
|
||||
line = self.browse(cr, uid, payment_line_id)
|
||||
reconcile_id = False
|
||||
self.ensure_one()
|
||||
reconcile_obj = self.env['account.move.reconcile']
|
||||
line = self
|
||||
reconcile = reconcile_obj.browse([])
|
||||
if (line.transit_move_line_id and not line.storno and
|
||||
self.pool.get('res.currency').is_zero(
|
||||
cr, uid, currency, (
|
||||
self.env['res.currency'].is_zero(
|
||||
currency, (
|
||||
(line.transit_move_line_id.credit or 0.0) -
|
||||
(line.transit_move_line_id.debit or 0.0) + amount))):
|
||||
# Two different cases, full and partial
|
||||
@@ -42,66 +41,60 @@ class PaymentLine(orm.Model):
|
||||
# we should not need to take partial into account on the side of
|
||||
# the transit_move_line.
|
||||
if line.transit_move_line_id.reconcile_partial_id:
|
||||
reconcile_id = \
|
||||
line.transit_move_line_id.reconcile_partial_id.id
|
||||
reconcile_partial = \
|
||||
line.transit_move_line_id.reconcile_partial_id
|
||||
reconcile = line.transit_move_line_id.reconcile_id
|
||||
if len(reconcile.line_partial_ids) == 2:
|
||||
# reuse the simple reconcile for the storno transfer
|
||||
reconcile_obj.write(
|
||||
cr, uid, reconcile_id,
|
||||
{'line_id': [(6, 0, line.transit_move_line_id.id)],
|
||||
'line_partial_ids': [(6, 0, [])]}, context=context)
|
||||
reconcile_partial.write({
|
||||
'line_id': [(6, 0, line.transit_move_line_id.id)],
|
||||
'line_partial_ids': [(6, 0, [])]
|
||||
})
|
||||
else:
|
||||
# split up the original reconcile in a partial one
|
||||
# and a new one for reconciling the storno transfer
|
||||
reconcile = {
|
||||
reconcile_partial.write({
|
||||
'line_partial_ids': [(3, line.transit_move_line_id.id)]
|
||||
}
|
||||
reconcile_obj.write(
|
||||
cr, uid, reconcile_id, reconcile, context=context)
|
||||
reconcile_id = reconcile_obj.create(
|
||||
cr, uid,
|
||||
{'type': 'auto',
|
||||
'line_id': [(6, 0, line.transit_move_line_id.id)]},
|
||||
context=context)
|
||||
})
|
||||
reconcile = reconcile_obj.create({
|
||||
'type': 'auto',
|
||||
'line_id': [(6, 0, line.transit_move_line_id.id)]
|
||||
})
|
||||
elif line.transit_move_line_id.reconcile_id:
|
||||
reconcile_id = line.transit_move_line_id.reconcile_id.id
|
||||
reconcile = line.transit_move_line_id.reconcile_id
|
||||
if len(line.transit_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.transit_move_line_id.id])]},
|
||||
context=context)
|
||||
reconcile.write({
|
||||
'line_id': [(6, 0, [line.transit_move_line_id.id])],
|
||||
})
|
||||
else:
|
||||
# split up the original reconcile in a partial one
|
||||
# and a new one for reconciling the storno transfer
|
||||
reconcile = line.transit_move_line_id.reconcile_id
|
||||
partial_ids = [x.id for x in reconcile.line_id
|
||||
if x.id != line.transit_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.transit_move_line_id.id)]},
|
||||
context=context)
|
||||
reconcile.write({
|
||||
'line_partial_ids': [(6, 0, partial_ids)],
|
||||
'line_id': [(6, 0, [])],
|
||||
})
|
||||
reconcile = reconcile_obj.create({
|
||||
'type': 'auto',
|
||||
'line_id': [(6, 0, line.transit_move_line_id.id)]
|
||||
})
|
||||
# mark the payment line for storno processed
|
||||
if reconcile_id:
|
||||
self.write(cr, uid, [payment_line_id],
|
||||
{'storno': True}, context=context)
|
||||
if reconcile:
|
||||
self.write({'storno': True})
|
||||
# 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
|
||||
workflow.trg_validate(
|
||||
self.env.uid, 'account.invoice',
|
||||
line.move_line_id.invoice.id, activity, self.env.cr)
|
||||
return reconcile.id
|
||||
|
||||
def get_storno_account_id(
|
||||
self, cr, uid, payment_line_id, amount, currency, context=None):
|
||||
@api.multi
|
||||
def get_storno_account_id(self, amount, currency):
|
||||
"""Check the match of the arguments, and return the account associated
|
||||
with the storno.
|
||||
Used in account_banking interactive mode
|
||||
@@ -112,34 +105,27 @@ class PaymentLine(orm.Model):
|
||||
: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)
|
||||
self.ensure_one()
|
||||
account_id = False
|
||||
if (line.transit_move_line_id and not line.storno and
|
||||
self.pool.get('res.currency').is_zero(
|
||||
cr, uid, currency, (
|
||||
(line.transit_move_line_id.credit or 0.0) -
|
||||
(line.transit_move_line_id.debit or 0.0) + amount))):
|
||||
account_id = line.transit_move_line_id.account_id.id
|
||||
if (self.transit_move_line_id and not self.storno and
|
||||
self.env['res.currency'].is_zero(
|
||||
currency, (
|
||||
(self.transit_move_line_id.credit or 0.0) -
|
||||
(self.transit_move_line_id.debit or 0.0) + amount))):
|
||||
account_id = self.transit_move_line_id.account_id.id
|
||||
return account_id
|
||||
|
||||
def debit_reconcile(self, cr, uid, payment_line_id, context=None):
|
||||
@api.one
|
||||
def debit_reconcile(self):
|
||||
"""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_vals = self.read(
|
||||
cr, uid, payment_line_id, ['storno', 'name'], context=context)
|
||||
if payment_line_vals['storno']:
|
||||
raise orm.except_orm(
|
||||
if self.storno:
|
||||
raise exceptions.except_orm(
|
||||
_('Can not reconcile'),
|
||||
_('Cancelation of payment line \'%s\' has already been '
|
||||
'processed') % payment_line_vals['name'])
|
||||
return super(PaymentLine, self).debit_reconcile(
|
||||
cr, uid, payment_line_id, context=context)
|
||||
'processed') % self.name)
|
||||
return super(PaymentLine, self).debit_reconcile()
|
||||
|
||||
_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")),
|
||||
}
|
||||
storno = fields.Boolean(
|
||||
'Storno', readonly=True,
|
||||
help="If this is true, the debit order has been canceled by the bank "
|
||||
"or by the customer")
|
||||
|
||||
Reference in New Issue
Block a user