mirror of
https://github.com/OCA/bank-payment.git
synced 2025-02-02 10:37:31 +02:00
Allow to select customer refunds in direct debit order and supplier refunds in payment order (to reduce the amount to pay)
This commit is contained in:
committed by
Pedro M. Baeza
parent
edcf09147e
commit
212f5f992e
@@ -130,7 +130,7 @@ class PaymentOrder(models.Model):
|
||||
# Delete existing bank payment lines
|
||||
order.bank_line_ids.unlink()
|
||||
# Create the bank payment lines from the payment lines
|
||||
group_paylines = {} # id = hashcode, value = payment lines
|
||||
group_paylines = {} # key = hashcode
|
||||
for payline in order.line_ids:
|
||||
# Compute requested payment date
|
||||
if order.date_prefered == 'due':
|
||||
@@ -143,11 +143,22 @@ class PaymentOrder(models.Model):
|
||||
payline.date = requested_date
|
||||
hashcode = payline.payment_line_hashcode()
|
||||
if hashcode in group_paylines:
|
||||
group_paylines[hashcode] += payline
|
||||
group_paylines[hashcode]['paylines'] += payline
|
||||
group_paylines[hashcode]['total'] +=\
|
||||
payline.amount_currency
|
||||
else:
|
||||
group_paylines[hashcode] = payline
|
||||
group_paylines[hashcode] = {
|
||||
'paylines': payline,
|
||||
'total': payline.amount_currency,
|
||||
}
|
||||
# Create bank payment lines
|
||||
for paylines in group_paylines.values():
|
||||
vals = self._prepare_bank_payment_line(paylines)
|
||||
for paydict in group_paylines.values():
|
||||
# Block if a bank payment line is <= 0
|
||||
if paydict['total'] <= 0:
|
||||
raise exceptions.Warning(
|
||||
_("The amount for Partner '%s' is negative (%.2f) !")
|
||||
% (paydict['paylines'][0].partner_id.name,
|
||||
paydict['total']))
|
||||
vals = self._prepare_bank_payment_line(paydict['paylines'])
|
||||
bplo.create(vals)
|
||||
return res
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
</xpath>
|
||||
<field name="mode" position="after">
|
||||
<field name="mode_type" invisible="1"/>
|
||||
<field name="payment_order_type" invisible="0"/>
|
||||
</field>
|
||||
<xpath expr="//button[@string='Invoices']" position="attributes">
|
||||
<attribute name="attrs">{
|
||||
@@ -30,7 +31,7 @@
|
||||
<notebook>
|
||||
<page string="Payment">
|
||||
<group col="4">
|
||||
<field name="move_line_id" on_change="onchange_move_line(move_line_id,parent.mode,parent.date_prefered,parent.date_scheduled,currency,company_currency)" domain="[('reconcile_id','=', False), ('credit', '>',0), ('account_id.reconcile', '=', True),('amount_to_pay','>', 0)] "/>
|
||||
<field name="move_line_id" on_change="onchange_move_line(move_line_id,parent.mode,parent.date_prefered,parent.date_scheduled,currency,company_currency)" domain="[('reconcile_id','=', False), ('account_id.reconcile', '=', True)] "/> <!-- we removed the filter on amount_to_pay, because we want to be able to select refunds -->
|
||||
<separator colspan="4" string="Transaction Information"/>
|
||||
<field name="date"/>
|
||||
<label for="amount_currency" string="Amount"/>
|
||||
|
||||
@@ -56,8 +56,7 @@ class PaymentOrderCreate(models.TransientModel):
|
||||
# Do not propose partially reconciled credit lines,
|
||||
# as they are deducted from a customer invoice, and
|
||||
# will not be refunded with a payment.
|
||||
domain += [('credit', '>', 0),
|
||||
'|',
|
||||
domain += ['|',
|
||||
('account_id.type', '=', 'payable'),
|
||||
'&',
|
||||
('account_id.type', '=', 'receivable'),
|
||||
@@ -171,7 +170,13 @@ class PaymentOrderCreate(models.TransientModel):
|
||||
# customer invoice number (in the case of debit order)
|
||||
communication = line.invoice.number.replace('/', '')
|
||||
state = 'structured'
|
||||
amount_currency = line.amount_residual_currency
|
||||
# support debit orders when enabled
|
||||
if line.debit > 0:
|
||||
amount_currency = line.amount_residual_currency * -1
|
||||
else:
|
||||
amount_currency = line.amount_residual_currency
|
||||
if payment.payment_order_type == 'debit':
|
||||
amount_currency *= -1
|
||||
line2bank = line.line2bank(payment.mode.id)
|
||||
# -- end account banking
|
||||
res = {'move_line_id': line.id,
|
||||
|
||||
@@ -104,8 +104,8 @@ class PaymentOrder(models.Model):
|
||||
"""
|
||||
res = []
|
||||
for order in self:
|
||||
for order_line in order.line_ids:
|
||||
move_line = order_line.transfer_move_line_id
|
||||
for bank_line in order.bank_line_ids:
|
||||
move_line = bank_line.transfer_move_line_id
|
||||
if move_line:
|
||||
res.append(move_line)
|
||||
return res
|
||||
@@ -141,23 +141,16 @@ class PaymentOrder(models.Model):
|
||||
|
||||
@api.multi
|
||||
def _prepare_move_line_transfer_account(
|
||||
self, amount, move, payment_lines, labels):
|
||||
if len(payment_lines) == 1:
|
||||
partner_id = payment_lines[0].partner_id.id
|
||||
name = _('%s line %s') % (labels[self.payment_order_type],
|
||||
payment_lines[0].name)
|
||||
if payment_lines[0].move_line_id.id and\
|
||||
payment_lines[0].move_line_id.move_id.state != 'draft':
|
||||
name = "%s (%s)" % (name,
|
||||
payment_lines[0].move_line_id.move_id.name)
|
||||
elif payment_lines[0].ml_inv_ref.id:
|
||||
name = "%s (%s)" % (name,
|
||||
payment_lines[0].ml_inv_ref.number)
|
||||
self, amount, move, bank_payment_lines, labels):
|
||||
if len(bank_payment_lines) == 1:
|
||||
partner_id = bank_payment_lines[0].partner_id.id
|
||||
name = _('%s bank line %s') % (labels[self.payment_order_type],
|
||||
bank_payment_lines[0].name)
|
||||
else:
|
||||
partner_id = False
|
||||
name = '%s %s' % (
|
||||
labels[self.payment_order_type], self.reference)
|
||||
date_maturity = payment_lines[0].date
|
||||
date_maturity = bank_payment_lines[0].date
|
||||
vals = {
|
||||
'name': name,
|
||||
'move_id': move.id,
|
||||
@@ -173,13 +166,12 @@ class PaymentOrder(models.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
|
||||
# TODO : ALEXIS check don't group if move_line_id.account_id
|
||||
# is not the same
|
||||
if self.payment_order_type == 'debit':
|
||||
account_id = line.partner_id.property_account_receivable.id
|
||||
else:
|
||||
if self.payment_order_type == 'debit':
|
||||
account_id = line.partner_id.property_account_receivable.id
|
||||
else:
|
||||
account_id = line.partner_id.property_account_payable.id
|
||||
account_id = line.partner_id.property_account_payable.id
|
||||
vals = {
|
||||
'name': _('%s line %s') % (
|
||||
labels[self.payment_order_type], line.name),
|
||||
@@ -187,9 +179,9 @@ class PaymentOrder(models.Model):
|
||||
'partner_id': line.partner_id.id,
|
||||
'account_id': account_id,
|
||||
'credit': (self.payment_order_type == 'debit' and
|
||||
line.amount or 0.0),
|
||||
line.amount_currency or 0.0),
|
||||
'debit': (self.payment_order_type == 'payment' and
|
||||
line.amount or 0.0),
|
||||
line.amount_currency or 0.0),
|
||||
}
|
||||
return vals
|
||||
|
||||
@@ -215,12 +207,12 @@ class PaymentOrder(models.Model):
|
||||
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()
|
||||
def _reconcile_payment_lines(self, bank_payment_lines):
|
||||
for bline in bank_payment_lines:
|
||||
if all([pline.move_line_id for pline in bline.payment_line_ids]):
|
||||
bline.debit_reconcile()
|
||||
else:
|
||||
self.action_sent_no_move_line_hook(line)
|
||||
self.action_sent_no_move_line_hook(bline)
|
||||
|
||||
@api.one
|
||||
def action_sent(self):
|
||||
@@ -232,8 +224,8 @@ class PaymentOrder(models.Model):
|
||||
am_obj = self.env['account.move']
|
||||
aml_obj = self.env['account.move.line']
|
||||
labels = {
|
||||
'payment': _('Payment order'),
|
||||
'debit': _('Direct debit order'),
|
||||
'payment': _('Payment'),
|
||||
'debit': _('Direct debit'),
|
||||
}
|
||||
if self.mode.transfer_journal_id and self.mode.transfer_account_id:
|
||||
# prepare a dict "trfmoves" that can be used when
|
||||
@@ -242,26 +234,21 @@ class PaymentOrder(models.Model):
|
||||
# value = [pay_line1, pay_line2, ...]
|
||||
trfmoves = {}
|
||||
if self.mode.transfer_move_option == 'line':
|
||||
for line in self.line_ids:
|
||||
for line in self.bank_line_ids:
|
||||
trfmoves[line.id] = [line]
|
||||
else:
|
||||
if self.date_prefered in ('now', 'fixed'):
|
||||
trfmoves[True] = []
|
||||
for line in self.line_ids:
|
||||
trfmoves[True].append(line)
|
||||
else: # date_prefered == due
|
||||
for line in self.line_ids:
|
||||
if line.date in trfmoves:
|
||||
trfmoves[line.date].append(line)
|
||||
else:
|
||||
trfmoves[line.date] = [line]
|
||||
for line in self.bank_line_ids:
|
||||
if line.date in trfmoves:
|
||||
trfmoves[line.date].append(line)
|
||||
else:
|
||||
trfmoves[line.date] = [line]
|
||||
|
||||
for identifier, lines in trfmoves.iteritems():
|
||||
mvals = self._prepare_transfer_move()
|
||||
move = am_obj.create(mvals)
|
||||
total_amount = 0
|
||||
for line in lines:
|
||||
total_amount += line.amount
|
||||
total_amount += line.amount_currency
|
||||
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(
|
||||
|
||||
@@ -35,29 +35,8 @@ class PaymentLine(models.Model):
|
||||
'''
|
||||
_inherit = 'payment.line'
|
||||
|
||||
@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:
|
||||
order_line.transfer_move_line_id = move_line
|
||||
elif order_type == 'payment' and move_line.credit > 0:
|
||||
order_line.transfer_move_line_id = move_line
|
||||
|
||||
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.
|
||||
@@ -98,6 +77,31 @@ class PaymentLine(models.Model):
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class BankPaymentLine(models.Model):
|
||||
_inherit = 'bank.payment.line'
|
||||
|
||||
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")
|
||||
|
||||
@api.multi
|
||||
def _get_transfer_move_line(self):
|
||||
for bank_line in self:
|
||||
if bank_line.transit_move_line_id:
|
||||
order_type = bank_line.order_id.payment_order_type
|
||||
trf_lines = bank_line.transit_move_line_id.move_id.line_id
|
||||
for move_line in trf_lines:
|
||||
if order_type == 'debit' and move_line.debit > 0:
|
||||
bank_line.transfer_move_line_id = move_line
|
||||
elif order_type == 'payment' and move_line.credit > 0:
|
||||
bank_line.transfer_move_line_id = move_line
|
||||
|
||||
@api.one
|
||||
def debit_reconcile(self):
|
||||
"""
|
||||
@@ -111,34 +115,32 @@ class PaymentLine(models.Model):
|
||||
"""
|
||||
|
||||
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 exceptions.except_orm(
|
||||
_('Can not reconcile'),
|
||||
_('No move line for line %s') % self.name
|
||||
)
|
||||
if torec_move_line.reconcile_id:
|
||||
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 exceptions.except_orm(
|
||||
_('Error'),
|
||||
_('Move line %s has already been reconciled') %
|
||||
transit_move_line.name
|
||||
)
|
||||
# if (not transit_move_line or not torec_move_line):
|
||||
# raise exceptions.UserError(
|
||||
# _('Can not reconcile: no move line for line %s') % self.name
|
||||
# )
|
||||
# if torec_move_line.reconcile_id:
|
||||
# raise exceptions.UserError(
|
||||
# _('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 exceptions.UserError(
|
||||
# _('Move line %s has already been reconciled') %
|
||||
# transit_move_line.name
|
||||
# )
|
||||
|
||||
line_ids = [transit_move_line.id, torec_move_line.id]
|
||||
self.env['account.move.line'].browse(line_ids).reconcile_partial(
|
||||
type='auto')
|
||||
lines_to_rec = transit_move_line
|
||||
for payment_line in self.payment_line_ids:
|
||||
lines_to_rec += payment_line.move_line_id
|
||||
|
||||
lines_to_rec.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(
|
||||
self.env.uid, 'account.invoice', torec_move_line.invoice.id,
|
||||
'undo_debit_denied', self.env.cr)
|
||||
# if torec_move_line.invoice:
|
||||
# workflow.trg_validate(
|
||||
# self.env.uid, 'account.invoice', torec_move_line.invoice.id,
|
||||
# 'undo_debit_denied', self.env.cr)
|
||||
|
||||
@@ -20,72 +20,12 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from operator import itemgetter
|
||||
from openerp.osv import fields, orm
|
||||
from openerp import models
|
||||
|
||||
|
||||
class AccountMoveLine(orm.Model):
|
||||
class AccountMoveLine(models.Model):
|
||||
_inherit = "account.move.line"
|
||||
|
||||
def _amount_to_receive(self, cr, uid, ids, name, arg=None, context=None):
|
||||
"""Return the amount still to receive regarding all the debit orders
|
||||
(excepting canceled orders).
|
||||
This is the reverse from amount_to_pay() in
|
||||
account_payment/account_move_line.py
|
||||
"""
|
||||
if not ids:
|
||||
return {}
|
||||
cr.execute("""SELECT ml.id,
|
||||
CASE WHEN ml.amount_currency > 0
|
||||
THEN ml.amount_currency
|
||||
ELSE ml.debit
|
||||
END -
|
||||
(SELECT coalesce(sum(amount_currency),0)
|
||||
FROM payment_line pl
|
||||
INNER JOIN payment_order po
|
||||
ON (pl.order_id = po.id)
|
||||
WHERE move_line_id = ml.id
|
||||
AND pl.storno is false
|
||||
AND po.state != 'cancel') AS amount
|
||||
FROM account_move_line ml
|
||||
WHERE id IN %s""", (tuple(ids),))
|
||||
r = dict(cr.fetchall())
|
||||
return r
|
||||
|
||||
def _to_receive_search(self, cr, uid, obj, name, args, context=None):
|
||||
"""Reverse of account_payment/account_move_line.py:_to_pay_search().
|
||||
"""
|
||||
if not args:
|
||||
return []
|
||||
line_obj = self.pool.get('account.move.line')
|
||||
search_context = dict(context or {}, all_fiscalyear=True)
|
||||
query = line_obj._query_get(cr, uid, context=search_context)
|
||||
where = ' and '.join(map(lambda x: '''(SELECT
|
||||
CASE WHEN l.amount_currency > 0
|
||||
THEN l.amount_currency
|
||||
ELSE l.debit
|
||||
END - coalesce(sum(pl.amount_currency), 0)
|
||||
FROM payment_line pl
|
||||
INNER JOIN payment_order po ON (pl.order_id = po.id)
|
||||
WHERE move_line_id = l.id
|
||||
AND pl.storno is false
|
||||
AND po.state != 'cancel'
|
||||
) %(operator)s %%s ''' % {'operator': x[1]}, args))
|
||||
sql_args = tuple(map(itemgetter(2), args))
|
||||
|
||||
cr.execute(('''SELECT id
|
||||
FROM account_move_line l
|
||||
WHERE account_id IN (select id
|
||||
FROM account_account
|
||||
WHERE type=%s AND active)
|
||||
AND reconcile_id IS null
|
||||
AND debit > 0
|
||||
AND ''' + where + ' and ' + query), ('receivable',) + sql_args)
|
||||
res = cr.fetchall()
|
||||
if not res:
|
||||
return [('id', '=', '0')]
|
||||
return [('id', 'in', map(lambda x:x[0], res))]
|
||||
|
||||
def line2bank(self, cr, uid, ids, payment_type=None, context=None):
|
||||
"""I have to inherit this function for direct debits to fix the
|
||||
following issue : if the customer invoice has a value for
|
||||
@@ -112,10 +52,3 @@ class AccountMoveLine(orm.Model):
|
||||
return line2bank
|
||||
return super(AccountMoveLine, self).line2bank(
|
||||
cr, uid, ids, payment_type=pay_mode.id, context=context)
|
||||
|
||||
_columns = {
|
||||
'amount_to_receive': fields.function(
|
||||
_amount_to_receive, method=True,
|
||||
type='float', string='Amount to receive',
|
||||
fnct_search=_to_receive_search),
|
||||
}
|
||||
|
||||
@@ -23,32 +23,27 @@
|
||||
<menuitem action="action_debit_order_tree" id="menu_action_debit_order_form" parent="account_payment.menu_main_payment" sequence="4"/>
|
||||
|
||||
<record id="view_payment_order_form" model="ir.ui.view">
|
||||
<field name="name">payment.order.form</field>
|
||||
<field name="name">direct.debit.payment.order.form</field>
|
||||
<field name="model">payment.order</field>
|
||||
<field name="inherit_id" ref="account_payment.view_payment_order_form"/>
|
||||
<field name="inherit_id" ref="account_banking_payment_export.view_payment_order_form"/>
|
||||
<field name="priority" eval="60"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<form position="inside">
|
||||
<field name="payment_order_type" invisible="1"/>
|
||||
</form>
|
||||
<button string="Invoices" position="attributes">
|
||||
<attribute name="attrs">{'invisible': ['|', ('state', '!=', 'draft'), ('payment_order_type', '!=', 'payment')]}</attribute>
|
||||
</button>
|
||||
<div class=" oe_right oe_button_box" position="inside">
|
||||
<button name="%(account_payment.action_create_payment_order)s"
|
||||
class="oe_inline oe_stat_button oe_right"
|
||||
string="Invoices"
|
||||
help="Select invoices to collect"
|
||||
type="action"
|
||||
attrs="{'invisible': ['|', ('state', '!=', 'draft'), ('payment_order_type', '!=', 'debit')]}"
|
||||
icon="fa-pencil-square-o"
|
||||
widget="statinfo"/>
|
||||
</div>
|
||||
<field name="mode" position="attributes">
|
||||
<attribute name="domain">[('payment_order_type', '=', payment_order_type)]</attribute>
|
||||
</field>
|
||||
</data>
|
||||
<button string="Invoices" position="attributes">
|
||||
<attribute name="attrs">{'invisible': ['|', ('state', '!=', 'draft'), ('payment_order_type', '!=', 'payment')]}</attribute>
|
||||
</button>
|
||||
<div class=" oe_right oe_button_box" position="inside">
|
||||
<button name="%(account_payment.action_create_payment_order)s"
|
||||
class="oe_inline oe_stat_button oe_right"
|
||||
string="Invoices"
|
||||
help="Select invoices to collect"
|
||||
type="action"
|
||||
attrs="{'invisible': ['|', ('state', '!=', 'draft'), ('payment_order_type', '!=', 'debit')]}"
|
||||
icon="fa-pencil-square-o"
|
||||
widget="statinfo"/>
|
||||
</div>
|
||||
<field name="mode" position="attributes">
|
||||
<attribute name="domain">[('payment_order_type', '=', payment_order_type)]</attribute>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -33,9 +33,13 @@ class PaymentOrderCreate(models.TransientModel):
|
||||
super(PaymentOrderCreate, self).extend_payment_order_domain(
|
||||
payment_order, domain)
|
||||
if payment_order.payment_order_type == 'debit':
|
||||
# With the new system with bank.payment.line, we want
|
||||
# to be able to have payment lines linked to customer
|
||||
# invoices and payment lines linked to customer refunds
|
||||
# in order to debit the customer of the total of his
|
||||
# invoices minus his refunds
|
||||
domain += ['|',
|
||||
('invoice', '=', False),
|
||||
('invoice.state', '!=', 'debit_denied'),
|
||||
('account_id.type', '=', 'receivable'),
|
||||
('amount_to_receive', '>', 0)]
|
||||
('account_id.type', '=', 'receivable')]
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user