mirror of
https://github.com/OCA/bank-payment.git
synced 2025-02-02 10:37:31 +02:00
[FIX] Bank import writes to browse object
[ADD] direct debit order: process storno during bank import [ADD] bank import: add hooks for processing debit orders and stornos [ADD] direct debit order: pre-select move lines on reference substring configured in payment mode [ADD] payment term for direct debit invoices [ADD] payment line views: add storno field [RFR] standardize storno and debit order processing during bank import
This commit is contained in:
@@ -36,6 +36,7 @@
|
||||
'view/account_payment.xml',
|
||||
'view/account_invoice.xml',
|
||||
'workflow/account_invoice.xml',
|
||||
'data/account_payment_term.xml',
|
||||
],
|
||||
'demo_xml': [],
|
||||
'description': '''
|
||||
|
||||
16
account_direct_debit/data/account_payment_term.xml
Normal file
16
account_direct_debit/data/account_payment_term.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data noupdate="1">
|
||||
<record id="payment_term_direct_debit" model="account.payment.term">
|
||||
<field name="name">Direct debit</field>
|
||||
<field name="note">Direct debit in 14 days</field>
|
||||
</record>
|
||||
<record id="payment_term_line_direct_debit" model="account.payment.term.line">
|
||||
<field name="name">Direct debit in 14 days</field>
|
||||
<field name="value">balance</field>
|
||||
<field eval="14" name="days"/>
|
||||
<field eval="0" name="days2"/>
|
||||
<field eval="payment_term_direct_debit" name="payment_id"/>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -19,6 +19,21 @@ class payment_mode(osv.osv):
|
||||
help=('Journal to write payment entries when confirming ' +
|
||||
'a debit order of this mode'),
|
||||
),
|
||||
'reference_filter': fields.char(
|
||||
'Reference filter', size=16,
|
||||
help=(
|
||||
'Optional substring filter on move line references. ' +
|
||||
'You can use this in combination with a specific journal ' +
|
||||
'for items that you want to handle with this mode. Use ' +
|
||||
'a separate sequence for the journal with a distinguished ' +
|
||||
'prefix or suffix and enter that character string here.'),
|
||||
),
|
||||
# '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()
|
||||
|
||||
@@ -77,14 +92,17 @@ class payment_order(osv.osv):
|
||||
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)
|
||||
import pdb
|
||||
pdb.set_trace()
|
||||
if is_zero(order.line_ids[0].debit_move_line_id,
|
||||
get_balance(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 action_sent(self, cr, uid, ids, context=None):
|
||||
@@ -165,86 +183,99 @@ payment_order()
|
||||
class payment_line(osv.osv):
|
||||
_inherit = 'payment.line'
|
||||
|
||||
def debit_storno(self, cr, uid, payment_line_id, storno_move_line_id, context=None):
|
||||
def debit_storno(self, cr, uid, payment_line_id, amount,
|
||||
currency_id, storno_retry=True, context=None):
|
||||
"""
|
||||
Process a payment line from a direct debit order which has
|
||||
been canceled by the bank or by the user:
|
||||
- Undo the reconciliation of the payment line with the move
|
||||
line that it originated from, and re-reconciliated with
|
||||
the credit payment in the bank journal of the same amount and
|
||||
on the same account.
|
||||
- Mark the payment line for being reversed.
|
||||
|
||||
:param payment_line_id: the single id of the canceled payment line
|
||||
:param storno_move_line_id: the credit payment in the bank journal
|
||||
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_id: the bank account's currency id
|
||||
: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.
|
||||
"""
|
||||
|
||||
if isinstance(payment_line_id, (list, tuple)):
|
||||
payment_line_id = payment_line_id[0]
|
||||
reconcile_obj = self.pool.get('account.move.reconcile')
|
||||
move_line_obj = self.pool.get('account.move.line')
|
||||
payment_line = self.browse(cr, uid, payment_line_id, context=context)
|
||||
|
||||
debit_move_line = payment_line.debit_move_line_id
|
||||
if (not debit_move_line):
|
||||
raise osv.except_osv(
|
||||
_('Can not process storno'),
|
||||
_('No move line for line %s') % payment_line.name)
|
||||
if payment_line.storno:
|
||||
raise osv.except_osv(
|
||||
_('Can not process storno'),
|
||||
_('Cancelation of payment line \'%s\' has already been ' +
|
||||
'processed') % payment_line.name)
|
||||
|
||||
def is_zero(total):
|
||||
return self.pool.get('res.currency').is_zero(
|
||||
cr, uid, debit_move_line.company_id.currency_id, total)
|
||||
|
||||
# check validity of the proposed move line
|
||||
torec_move_line = move_line_obj.browse(
|
||||
cr, uid, storno_move_line_id, context=context)
|
||||
if not (is_zero(torec_move_line.debit - debit_move_line.debit) and
|
||||
is_zero(torec_move_line.credit - debit_move_line.credit) and
|
||||
torec_move_line.account_id.id == debit_move_line.account_id.id):
|
||||
raise osv.except_osv(
|
||||
_('Can not process storno'),
|
||||
_('%s is not a drop-in replacement for %s') % (
|
||||
torec_move_line.name, debit_move_line.name))
|
||||
if payment_line.storno:
|
||||
raise osv.except_osv(
|
||||
_('Can not process storno'),
|
||||
_('Debit order line %s has already been cancelled') % (
|
||||
payment_line.name))
|
||||
|
||||
# replace move line in reconciliation
|
||||
reconcile_obj = self.pool.get('account.move.reconcile')
|
||||
line = self.browse(cr, uid, payment_line_id)
|
||||
reconcile_id = False
|
||||
if (payment_line.move_line_id.reconcile_partial_id and
|
||||
debit_move_line_id.id in
|
||||
payment_line.move_line_id.reconcile_partial_id.line_partial_ids):
|
||||
reconcile_id = payment_line.move_line_id.reconcile_partial_id
|
||||
vals = {
|
||||
'line_partial_ids':
|
||||
[(3, debit_move_line_id.id), (4, torec_move_line.id)],
|
||||
}
|
||||
elif (payment_line.move_line_id.reconcile_id and
|
||||
debit_move_line_id.id in
|
||||
payment_line.move_line_id.reconcile_id.line_id):
|
||||
reconcile_id = payment_line.move_line_id.reconcile_id
|
||||
vals = {
|
||||
'line_id':
|
||||
[(3, debit_move_line_id.id), (4, torec_move_line.id)]
|
||||
}
|
||||
if not reconcile_id:
|
||||
raise osv.except_osv(
|
||||
_('Can not perform storno'),
|
||||
_('Debit order line %s does not occur in the list of '
|
||||
'reconciliation move lines of its origin') %
|
||||
debit_move_line_id.name)
|
||||
reconcile_obj.write(cr, uid, reconcile_id, vals, context=context)
|
||||
self.write(cr, uid, payment_line_id, {'storno': True}, context=context)
|
||||
#for line_id in line_ids:
|
||||
# netsvc.LocalService("workflow").trg_trigger(
|
||||
# uid, 'account.move.line', line_id, cr)
|
||||
if (line.debit_move_line_id and not line.storno and
|
||||
self.pool.get('res.currency').is_zero(
|
||||
cr, uid, currency_id, (
|
||||
(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?
|
||||
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 debit_reconcile(self, cr, uid, payment_line_id, context=None):
|
||||
"""
|
||||
@@ -356,9 +387,29 @@ class payment_order_create(osv.osv_memory):
|
||||
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'), ('amount_to_receive', '>', 0)]
|
||||
domain = [
|
||||
('reconcile_id', '=', False),
|
||||
('account_id.type', '=', 'receivable'),
|
||||
('amount_to_receive', '>', 0),
|
||||
]
|
||||
# cannot filter on properties of (searchable)
|
||||
# function fields. Needs work in expression.expression.parse()
|
||||
# Currently gives an SQL error.
|
||||
# # apply payment term filter
|
||||
# if payment.mode.payment_term_ids:
|
||||
# term_ids = [term.id for term in payment.mode.payment_term_ids]
|
||||
# domain = domain + [
|
||||
# '|', ('invoice', '=', False),
|
||||
# ('invoice.payment_term', 'in', term_ids),
|
||||
# ]
|
||||
else:
|
||||
domain = [('reconcile_id', '=', False), ('account_id.type', '=', 'payable'), ('amount_to_pay', '>', 0)]
|
||||
domain = [
|
||||
('reconcile_id', '=', False),
|
||||
('account_id.type', '=', 'payable'),
|
||||
('amount_to_pay', '>', 0)
|
||||
]
|
||||
if payment.mode.reference_filter:
|
||||
domain.append(('ref', 'ilike', payment.mode.reference_filter))
|
||||
# domain = [('reconcile_id', '=', False), ('account_id.type', '=', 'payable'), ('amount_to_pay', '>', 0)]
|
||||
### end account_direct_debit ###
|
||||
|
||||
|
||||
@@ -45,6 +45,10 @@
|
||||
icon="gtk-find"
|
||||
/>
|
||||
</xpath>
|
||||
<xpath expr="//tree[@string='Payment Line']" position="inside">
|
||||
<!-- the attrs do not work like this, apparently -->
|
||||
<field name="storno" attrs="{'invisible': [(parent.payment_order_type, '!=', 'debit')]}"/>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
@@ -59,8 +63,22 @@
|
||||
<field name="type" position="after">
|
||||
<field name="transfer_account_id"/>
|
||||
<field name="transfer_journal_id"/>
|
||||
<field name="reference_filter"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_payment_line_tree" model="ir.ui.view">
|
||||
<field name="name">Payment Lines</field>
|
||||
<field name="model">payment.line</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="inherit_id" ref="account_payment.view_payment_line_tree"/>
|
||||
<field eval="4" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="name" position="after">
|
||||
<field name="storno"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<record id="debit_denied_to_open" model="workflow.transition">
|
||||
<field name="act_from" ref="act_debit_denied"/>
|
||||
<field name="act_to" ref="account.act_open_test"/>
|
||||
<field name="signal">open_test</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
Reference in New Issue
Block a user