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:
Alexis de Lattre
2015-09-23 18:18:37 +02:00
committed by Pedro M. Baeza
parent edcf09147e
commit 212f5f992e
8 changed files with 130 additions and 192 deletions

View File

@@ -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

View File

@@ -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"/>

View File

@@ -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,

View File

@@ -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(

View File

@@ -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)

View File

@@ -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),
}

View File

@@ -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>

View File

@@ -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