Merge pull request #110 from acsone/8.0-bank-payment-fix93-sbi

8.0 improve selection of invoices to pay, solves #93
This commit is contained in:
Pedro M. Baeza
2015-04-08 09:07:54 +02:00
10 changed files with 738 additions and 81 deletions

View File

@@ -19,8 +19,7 @@
#
##############################################################################
from openerp.osv import orm, fields
from operator import itemgetter
from openerp.osv import orm
class AccountMoveLine(orm.Model):
@@ -40,76 +39,3 @@ class AccountMoveLine(orm.Model):
cr, uid, ids, ['debit', 'credit'], context=context):
total += (line['debit'] or 0.0) - (line['credit'] or 0.0)
return total
# All the code below aims at fixing one small issue in _to_pay_search()
# But _to_pay_search() is the search function of the field 'amount_to_pay'
# which is a field.function and these functions are not inheritable in
# OpenERP.
# So we have to inherit the field 'amount_to_pay' and duplicate the related
# functions
# If the patch that I proposed in this bug report
# https://bugs.launchpad.net/openobject-addons/+bug/1275478
# is integrated in addons/account_payment, then we will be able to remove
# this file. -- Alexis de Lattre
def _amount_to_pay(self, cr, uid, ids, name, arg=None, context=None):
""" Return the amount still to pay regarding all the payment orders
(excepting cancelled orders)"""
if not ids:
return {}
cr.execute("""SELECT ml.id,
CASE WHEN ml.amount_currency < 0
THEN - ml.amount_currency
ELSE ml.credit
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 po.state != 'cancel') AS amount
FROM account_move_line ml
WHERE id IN %s""", (tuple(ids),))
r = dict(cr.fetchall())
return r
def _to_pay_search(self, cr, uid, obj, name, args, context=None):
if not args:
return []
line_obj = self.pool.get('account.move.line')
query = line_obj._query_get(cr, uid, context={})
where = ' and '.join(map(lambda x: '''(SELECT
CASE WHEN l.amount_currency < 0
THEN - l.amount_currency
ELSE l.credit
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 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 in %s AND active)
AND reconcile_id IS null
AND credit > 0
AND ''' + where + ' and ' + query,
(('payable', 'receivable'),) + sql_args)
# The patch we have compared to the original function in
# addons/account_payment is just above :
# original code : type = 'payable'
# fixed code : type in ('payable', 'receivable')
res = cr.fetchall()
if not res:
return [('id', '=', '0')]
return [('id', 'in', map(lambda x:x[0], res))]
_columns = {
'amount_to_pay': fields.function(
_amount_to_pay, type='float', string='Amount to pay',
fnct_search=_to_pay_search),
}

View File

@@ -3,6 +3,7 @@
#
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
# (C) 2014 - 2015 ACSONE SA/NV (<http://acsone.eu>).
#
# All other contributions are (C) by their respective contributors
#
@@ -44,9 +45,46 @@ class PaymentOrderCreate(models.TransientModel):
@api.model
def extend_payment_order_domain(self, payment_order, domain):
if payment_order.payment_order_type == 'payment':
domain += [('account_id.type', 'in', ('payable', 'receivable')),
('amount_to_pay', '>', 0)]
return True
# For payables, propose all unreconciled credit lines,
# including partially reconciled ones.
# If they are partially reconciled with a supplier refund,
# the residual will be added to the payment order.
#
# For receivables, propose all unreconciled credit lines.
# (ie customer refunds): they can be refunded with a payment.
# 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),
'|',
('account_id.type', '=', 'payable'),
'&',
('account_id.type', '=', 'receivable'),
('reconcile_partial_id', '=', False)]
@api.model
def filter_lines(self, lines):
""" Filter move lines before proposing them for inclusion
in the payment order.
This implementation filters out move lines that are already
included in draft or open payment orders. This prevents the
user to include the same line in two different open payment
orders. When the payment order is sent, it is assumed that
the move will be reconciled soon (or immediately with
account_banking_payment_transfer), so it will not be
proposed anymore for payment.
See also https://github.com/OCA/bank-payment/issues/93.
:param lines: recordset of move lines
:returns: list of move line ids
"""
payment_lines = self.env['payment.line'].\
search([('order_id.state', 'in', ('draft', 'open')),
('move_line_id', 'in', lines.ids)])
to_exclude = set([l.move_line_id.id for l in payment_lines])
return [l.id for l in lines if l.id not in to_exclude]
@api.multi
def search_entries(self):
@@ -69,7 +107,7 @@ class PaymentOrderCreate(models.TransientModel):
# -- end account_direct_debit --
lines = line_obj.search(domain)
context = self.env.context.copy()
context['line_ids'] = lines.ids
context['line_ids'] = self.filter_lines(lines)
context['populate_results'] = self.populate_results
model_datas = model_data_obj.search(
[('model', '=', 'ir.ui.view'),
@@ -129,7 +167,7 @@ class PaymentOrderCreate(models.TransientModel):
'amount_to_receive' in line):
amount_currency = line.amount_to_receive
else:
amount_currency = line.amount_to_pay
amount_currency = line.amount_residual_currency
line2bank = line.line2bank(payment.mode.id)
# -- end account banking
res = {'move_line_id': line.id,

View File

@@ -37,6 +37,15 @@
'data': [
'view/payment_mode.xml',
'workflow/account_payment.xml',
'view/account_payment.xml',
],
'test': [
'test/data.yml',
'test/test_payment_method.yml',
'test/test_partial_payment_refunded.yml',
'test/test_partial_payment_transfer.yml',
],
'description': '''Payment order reconciliation infrastructure

View File

@@ -34,6 +34,18 @@ class PaymentOrder(models.Model):
'''
_inherit = 'payment.order'
@api.multi
def get_partial_reconcile_ids(self):
self.ensure_one()
reconcile_partial_ids = [line.move_line_id.reconcile_partial_id.id
for line in self.line_ids if
line.move_line_id.reconcile_partial_id.id]
return reconcile_partial_ids
@api.one
def get_partial_reconcile_count(self):
self.partial_reconcile_count = len(self.get_partial_reconcile_ids())
date_scheduled = fields.Date(states={
'sent': [('readonly', True)],
'rejected': [('readonly', True)],
@@ -75,6 +87,9 @@ class PaymentOrder(models.Model):
'done': [('readonly', True)]
})
date_sent = fields.Date(string='Send date', readonly=True)
partial_reconcile_count = fields\
.Integer(string='Partial Reconciles Counter',
compute='get_partial_reconcile_count')
def action_rejected(self, cr, uid, ids, context=None):
return True
@@ -253,3 +268,22 @@ class PaymentOrder(models.Model):
# State field is written by act_sent_wait
self.write({'date_sent': fields.Date.context_today(self)})
return True
@api.multi
def partial(self):
self.ensure_one()
view_id = self.env.ref('account.view_move_line_tree').id
reconcile_partial_ids = self.get_partial_reconcile_ids()
reconcile_partial_domain = [('reconcile_partial_id', 'in',
reconcile_partial_ids)]
return {
'name': _('Partial Reconcile Moves Line'),
'context': self.env.context,
'domain': reconcile_partial_domain,
'view_type': 'form',
'view_mode': 'tree,form',
'res_model': 'account.move.line',
'views': [(view_id, 'tree')],
'type': 'ir.actions.act_window',
'target': 'current',
}

View File

@@ -177,7 +177,7 @@ class PaymentLine(orm.Model):
if torec_move_line.reconcile_partial_id:
reconcile_obj.write(
cr, uid, transit_move_line.reconcile_partial_id.id,
cr, uid, [torec_move_line.reconcile_partial_id.id],
vals, context=context)
else:
reconcile_obj.create(

View File

@@ -0,0 +1,28 @@
-
I create a transfer account
-
!record {model: account.account, id: account_account_transfer0}:
code: TRANSF
name: Transfer
user_type: account.data_account_type_liability
type: other
reconcile: True
-
I create a transfer journal
-
!record {model: account.journal, id: transfer_journal0}:
name: Transfer journal
code: TR
type: general
company_id: base.main_company
-
I create a payment mode
-
!record {model: payment.mode, id: payment_mode0}:
name: Payment Mode Test
journal: account.bank_journal
bank_id: account_payment.partner_bank_1
company_id: base.main_company
transfer_account_id: account_account_transfer0
transfer_journal_id: transfer_journal0
type: account_banking_payment_export.manual_bank_tranfer

View File

@@ -0,0 +1,162 @@
-
I create a supplier invoice
-
!record {model: account.invoice, id: account_invoice_supplier_refunded, view: account.invoice_supplier_form}:
check_total: 600.00
partner_id: base.res_partner_12
reference_type: none
type: in_invoice
account_id: account.a_pay
company_id: base.main_company
currency_id: base.EUR
invoice_line:
- account_id: account.a_expense
name: 'Some contact lenses'
price_unit: 600.00
quantity: 1.0
journal_id: account.expenses_journal
-
Make sure that the type is in_invoice
-
!python {model: account.invoice}: |
self.write(cr, uid, ref("account_invoice_supplier_refunded"), {'type': 'in_invoice'})
-
I change the state of invoice to open by clicking Validate button
-
!workflow {model: account.invoice, action: invoice_open, ref: account_invoice_supplier_refunded}
-
I create a supplier refund for this invoice
-
!record {model: account.invoice, id: account_refund_supplier_refunded, view: account.invoice_supplier_form}:
check_total: 200.00
partner_id: base.res_partner_12
reference_type: none
type: in_refund
account_id: account.a_pay
company_id: base.main_company
currency_id: base.EUR
invoice_line:
- account_id: account.a_expense
name: 'Some contact lenses'
price_unit: 200.00
quantity: 1.0
journal_id: account.expenses_journal
-
Make sure that the type is in_invoice
-
!python {model: account.invoice}: |
self.write(cr, uid, ref("account_refund_supplier_refunded"), {'type': 'in_refund'})
-
I change the state of invoice to open by clicking Validate button
-
!workflow {model: account.invoice, action: invoice_open, ref: account_refund_supplier_refunded}
-
I reconcile the invoice and the refund
-
!record {model: account.move.line.reconcile, id: account_move_line_reconcile0}:
trans_nbr: 2
credit: 600.0
debit: 200.0
writeoff: -400.0
-
Then I click on the 'Partial Reconcile' button
-
!python {model: account.move.line.reconcile}: |
move_line_obj = self.pool.get('account.move.line')
inv_obj = self.pool.get('account.invoice')
invoice_move_id = inv_obj.browse(cr, uid, ref("account_invoice_supplier_refunded")).move_id.id
refund_move_id = inv_obj.browse(cr, uid, ref("account_refund_supplier_refunded")).move_id.id
debit_line_id = move_line_obj.search(cr, uid, [('move_id', '=', refund_move_id),('debit', '=', 200)])[0]
credit_line_id = move_line_obj.search(cr, uid, [('move_id', '=', invoice_move_id),('credit', '=', 600)])[0]
ids = [debit_line_id, credit_line_id]
partial_reconcile = self.trans_rec_reconcile_partial_reconcile(cr, uid, [ref('account_move_line_reconcile0')], {
'active_model': 'account.move.line', 'active_ids': ids, 'tz': False, 'active_id': ids[1]})
move_line = move_line_obj.browse(cr, uid, ids)
assert move_line[0].reconcile_partial_id, "Partial reconcilation is not done"
-
I check that the invoice balance (residual) is now 400
-
!assert {model: account.invoice, id: account_invoice_supplier_refunded, severity: error, string: Invoice residual should be 400.}:
- residual == 400
- amount_total == 600
-
I create a payment order on which I will select the invoice I created
-
!record {model: payment.order, id: partial_payment_order_1}:
mode: account_banking_payment_transfer.payment_mode0
date_prefered: 'due'
-
!record {model: payment.order.create, id: payment_order_create_1}:
duedate: !eval time.strftime('%Y-%m-%d')
-
I search for the invoice entries to make the payment.
-
!python {model: payment.order.create}: |
self.search_entries(cr, uid, [ref("payment_order_create_1")], {
"active_model": "payment.order", "active_ids": [ref("partial_payment_order_1")],
"active_id": ref("partial_payment_order_1"), })
-
I create payment lines entries.
-
!python {model: payment.order.create}: |
invoice = self.pool.get('account.invoice').browse(cr, uid, ref("account_invoice_supplier_refunded"))
for l in invoice.move_id.line_id:
if not l.debit and l.credit:
move_line = l
break
self.write(cr, uid, [ref("payment_order_create_1")], {'entries': [(6,0,[move_line.id])]})
self.create_payment(cr, uid, [ref("payment_order_create_1")], {
"active_model": "payment.order", "active_ids": [ref("partial_payment_order_1")],
"active_id": ref("partial_payment_order_1")})
pay_obj = self.pool.get('payment.order')
pay = pay_obj.browse(cr, uid, ref('partial_payment_order_1'))
assert pay.line_ids[0].amount_currency == 400
assert pay.total == 400
-
I confirm the payment order.
-
!workflow {model: payment.order, action: open, ref: partial_payment_order_1}
-
I check that payment order is now "Confirmed".
-
!assert {model: payment.order, id: partial_payment_order_1, severity: error, string: Payment Order should be 'Confirmed'.}:
- state == 'open'
-
I create the wizard for paying the payment
-
!record {model: payment.manual, id: payment_manual_partial}:
create_date: !eval time.strftime('%Y-%m-%d')
-
I click OK
-
!python {model: payment.manual}: |
if context is None:
context = {}
context.update({'active_ids': [ref("partial_payment_order_1")]})
self.button_ok(cr, uid, ref("payment_manual_partial"), context)
-
I check that the payment order is now "Sent".
-
!assert {model: payment.order, id: partial_payment_order_1, severity: error, string: Payment Order should be 'Sent'.}:
- state == 'sent'
-
I check that the invoice has payments associated
-
!assert {model: account.invoice, id: account_invoice_supplier_refunded, severity: error, string: payment_ids should be populated}:
- payment_ids
-
I check the content of the payment of the invoice
-
!python {model: account.invoice}: |
inv = self.browse(cr, uid, ref("account_invoice_supplier_refunded"))
payment1, payment2 = sorted(inv.payment_ids, key=lambda line: line.id)
assert payment1.debit == 200
assert payment2.debit == 400
assert inv.payment_ids[0].reconcile_id.id != False
-
I check that the invoice balance (residual) is now 0 and the state is paid
-
!assert {model: account.invoice, id: account_invoice_supplier_refunded, severity: error, string: Invoice residual should be 0.}:
- residual == 0
- amount_total == 600
- state == 'paid'

View File

@@ -0,0 +1,276 @@
-
I create a supplier invoice
-
!record {model: account.invoice, id: account_invoice_supplier_partial, view: account.invoice_supplier_form}:
check_total: 1000.00
partner_id: base.res_partner_12
reference_type: none
type: in_invoice
account_id: account.a_pay
company_id: base.main_company
currency_id: base.EUR
invoice_line:
- account_id: account.a_expense
name: 'Some glasses'
price_unit: 1000.00
quantity: 1.0
journal_id: account.expenses_journal
-
Make sure that the type is in_invoice
-
!python {model: account.invoice}: |
self.write(cr, uid, ref("account_invoice_supplier_partial"), {'type': 'in_invoice'})
-
I change the state of invoice to open by clicking Validate button
-
!workflow {model: account.invoice, action: invoice_open, ref: account_invoice_supplier_partial}
-
I create a payment order on which I will select the invoice I created
-
!record {model: payment.order, id: partial_payment_order_2}:
mode: account_banking_payment_transfer.payment_mode0
date_prefered: 'due'
-
!record {model: payment.order.create, id: payment_order_create_2}:
duedate: !eval time.strftime('%Y-%m-%d')
-
I search for the invoice entries to make the payment.
-
!python {model: payment.order.create}: |
self.search_entries(cr, uid, [ref("payment_order_create_2")], {
"active_model": "payment.order", "active_ids": [ref("partial_payment_order_2")],
"active_id": ref("partial_payment_order_2"), })
-
I create payment lines entries.
-
!python {model: payment.order.create}: |
invoice = self.pool.get('account.invoice').browse(cr, uid, ref("account_invoice_supplier_partial"))
move_line = invoice.move_id.line_id[0]
self.write(cr, uid, [ref("payment_order_create_2")], {'entries': [(6,0,[move_line.id])]})
self.create_payment(cr, uid, [ref("payment_order_create_2")], {
"active_model": "payment.order", "active_ids": [ref("partial_payment_order_2")],
"active_id": ref("partial_payment_order_2")})
pay_obj = self.pool.get('payment.order')
pay = pay_obj.browse(cr, uid, ref('partial_payment_order_2'))
assert pay.line_ids
assert pay.line_ids[0].amount_currency == 1000.0
-
I change the amount paid to test the partial payment
-
!python {model: payment.order}: |
line_ids = self.browse(cr, uid, ref('partial_payment_order_2')).line_ids
line_to_change = line_ids[0]
assert line_to_change.amount_currency == 1000.00
self.pool.get('payment.line').write(cr, uid, line_to_change.id, {'amount_currency':100})
-
I confirm the payment order.
-
!workflow {model: payment.order, action: open, ref: partial_payment_order_2}
-
I check that payment order is now "Confirmed".
-
!assert {model: payment.order, id: partial_payment_order_2, severity: error, string: Payment Order should be 'Confirmed'.}:
- state == 'open'
-
I assume that the document is sent to the bank and validate.
-
!record {model: payment.manual, id: payment_manual_1}:
create_date: !eval time.strftime('%Y-%m-%d')
-
I click OK
-
!python {model: payment.manual}: |
if context is None:
context = {}
context.update({'active_ids': [ref("partial_payment_order_2")]})
self.button_ok(cr, uid, ref("payment_manual_1"), context)
-
I check that the payment order is now "Sent".
-
!assert {model: payment.order, id: partial_payment_order_2, severity: error, string: Payment Order should be 'Sent'.}:
- state == 'sent'
-
I check that the invoice has payments associated
-
!assert {model: account.invoice, id: account_invoice_supplier_partial, severity: error, string: payment_ids should be populated}:
- payment_ids
-
I check the content of the payment of the invoice
-
!python {model: account.invoice}: |
inv = self.browse(cr, uid, ref("account_invoice_supplier_partial"))
assert round(inv.payment_ids[0].debit, 2) == 100
assert inv.payment_ids[0].credit == 0
assert not inv.payment_ids[0].reconcile_id.id
assert inv.payment_ids[0].reconcile_partial_id
sum_debit = 0.0
sum_credit = 0.0
for line in inv.payment_ids[0].reconcile_partial_id.line_partial_ids:
sum_debit += line.debit
sum_credit += line.credit
assert sum_debit == 100
sum_credit == 1000
assert inv.residual == 900
assert inv.state == 'open'
-
I create a 2nd partial payment
-
!record {model: payment.order, id: partial_partial_payment_order_2}:
mode: account_banking_payment_transfer.payment_mode0
date_prefered: 'due'
-
!record {model: payment.order.create, id: partial_payment_order_create_2}:
duedate: !eval time.strftime('%Y-%m-%d')
-
I search for the invoice entries to make the payment.
-
!python {model: payment.order.create}: |
self.search_entries(cr, uid, [ref("partial_payment_order_create_2")], {
"active_model": "payment.order", "active_ids": [ref("partial_partial_payment_order_2")],
"active_id": ref("partial_partial_payment_order_2"), })
-
I create payment lines entries.
-
!python {model: payment.order.create}: |
invoice = self.pool.get('account.invoice').browse(cr, uid, ref("account_invoice_supplier_partial"))
for l in invoice.move_id.line_id:
if not l.debit and l.credit:
move_line = l
break
self.write(cr, uid, [ref("partial_payment_order_create_2")], {'entries': [(6,0,[move_line.id])]})
self.create_payment(cr, uid, [ref("partial_payment_order_create_2")], {
"active_model": "payment.order", "active_ids": [ref("partial_partial_payment_order_2")],
"active_id": ref("partial_partial_payment_order_2")})
pay_obj = self.pool.get('payment.order')
pay = pay_obj.browse(cr, uid, ref('partial_partial_payment_order_2'))
assert len(pay.line_ids) == 1
assert pay.line_ids[0].amount_currency == 900
-
I change the amount paid to test the partial payment
-
!python {model: payment.order}: |
line_ids = self.browse(cr, uid, ref('partial_partial_payment_order_2')).line_ids
line_to_change = line_ids[0]
self.pool.get('payment.line').write(cr, uid, line_to_change.id, {'amount_currency':200})
-
I confirm the payment order.
-
!workflow {model: payment.order, action: open, ref: partial_partial_payment_order_2}
-
I assume that the document is sent to the bank and validate.
-
!record {model: payment.manual, id: payment_manual_1}:
create_date: !eval time.strftime('%Y-%m-%d')
-
I click OK
-
!python {model: payment.manual}: |
if context is None:
context = {}
context.update({'active_ids': [ref("partial_partial_payment_order_2")]})
self.button_ok(cr, uid, ref("payment_manual_1"), context)
-
I check that the payment order is now "Sent".
-
!assert {model: payment.order, id: partial_partial_payment_order_2, severity: error, string: Payment Order should be 'Sent'.}:
- state == 'sent'
-
I check the content of the payment of the invoice
-
!python {model: account.invoice}: |
inv = self.browse(cr, uid, ref("account_invoice_supplier_partial"))
assert len(inv.payment_ids) == 2
assert inv.payment_ids[0].credit == 0
assert not inv.payment_ids[0].reconcile_id.id
assert inv.payment_ids[0].reconcile_partial_id
sum_debit = 0.0
sum_credit = 0.0
for line in inv.payment_ids[0].reconcile_partial_id.line_partial_ids:
sum_debit += line.debit
sum_credit += line.credit
assert sum_debit == 300
assert sum_credit == 1000
assert inv.residual == 700
assert inv.state == 'open'
-
I create the last partial payment for completing the payment
-
!record {model: payment.order, id: partial_partial_payment_order_3}:
mode: account_banking_payment_transfer.payment_mode0
date_prefered: 'due'
-
!record {model: payment.order.create, id: partial_payment_order_create_3}:
duedate: !eval time.strftime('%Y-%m-%d')
-
I search for the invoice entries to make the payment.
-
!python {model: payment.order.create}: |
self.search_entries(cr, uid, [ref("partial_payment_order_create_3")], {
"active_model": "payment.order", "active_ids": [ref("partial_partial_payment_order_3")],
"active_id": ref("partial_partial_payment_order_3"), })
-
I create payment lines entries.
-
!python {model: payment.order.create}: |
invoice = self.pool.get('account.invoice').browse(cr, uid, ref("account_invoice_supplier_partial"))
for l in invoice.move_id.line_id:
if not l.debit and l.credit:
move_line = l
break
self.write(cr, uid, [ref("partial_payment_order_create_3")], {'entries': [(6,0,[move_line.id])]})
self.create_payment(cr, uid, [ref("partial_payment_order_create_3")], {
"active_model": "payment.order", "active_ids": [ref("partial_partial_payment_order_3")],
"active_id": ref("partial_partial_payment_order_3")})
pay_obj = self.pool.get('payment.order')
pay = pay_obj.browse(cr, uid, ref('partial_partial_payment_order_3'))
assert len(pay.line_ids) == 1
assert pay.line_ids[0].amount_currency == 700
-
I confirm the payment order.
-
!workflow {model: payment.order, action: open, ref: partial_partial_payment_order_3}
-
I assume that the document is sent to the bank and validate.
-
!record {model: payment.manual, id: payment_manual_3}:
create_date: !eval time.strftime('%Y-%m-%d')
-
I click OK
-
!python {model: payment.manual}: |
if context is None:
context = {}
context.update({'active_ids': [ref("partial_partial_payment_order_3")]})
self.button_ok(cr, uid, ref("payment_manual_3"), context)
-
I check that the payment order is now "Sent".
-
!assert {model: payment.order, id: partial_partial_payment_order_3, severity: error, string: Payment Order should be 'Sent'.}:
- state == 'sent'
-
I check the content of the payment of the invoice
-
!python {model: account.invoice}: |
inv = self.browse(cr, uid, ref("account_invoice_supplier_partial"))
assert len(inv.payment_ids) == 3
assert inv.payment_ids[0].credit == 0
assert inv.payment_ids[0].reconcile_id.id
#assert not inv.payment_ids[0].reconcile_partial_id ?? should we remove it?
sum_debit = 0.0
sum_credit = 0.0
for line in inv.payment_ids:
sum_debit += line.debit
sum_credit += line.credit
assert sum_debit == 1000
assert sum_credit == 0
assert inv.residual == 0
assert inv.state == 'paid'

View File

@@ -0,0 +1,164 @@
-
I create a supplier invoice
-
!record {model: account.invoice, id: account_invoice_supplier0, view: account.invoice_supplier_form}:
check_total: 450.0
partner_id: base.res_partner_4
reference_type: none
type: in_invoice
account_id: account.a_pay
company_id: base.main_company
currency_id: base.EUR
invoice_line:
- account_id: account.a_expense
name: 'Some expenses'
price_unit: 450.0
quantity: 1.0
- account_id: account.a_expense
name: 'Some other expenses'
price_unit: 555.55
quantity: 1.0
journal_id: account.expenses_journal
-
Make sure that the type is in_invoice
-
!python {model: account.invoice}: |
self.write(cr, uid, ref("account_invoice_supplier0"), {'type': 'in_invoice'})
-
I change the state of invoice to open by clicking Validate button
-
!workflow {model: account.invoice, action: invoice_open, ref: account_invoice_supplier0}
-
I check that the invoice state is now "Open"
-
!assert {model: account.invoice, id: account_invoice_supplier0}:
- state == 'open'
- type == 'in_invoice'
-
I create a payment order on which I will select the invoice I created
-
!record {model: payment.order, id: payment_order_0}:
mode: account_banking_payment_transfer.payment_mode0
date_prefered: 'due'
-
!record {model: payment.order.create, id: payment_order_create_0}:
duedate: !eval time.strftime('%Y-%m-%d')
-
I search for the invoice entries to make the payment.
-
!python {model: payment.order.create}: |
self.search_entries(cr, uid, [ref("payment_order_create_0")], {
"active_model": "payment.order", "active_ids": [ref("payment_order_0")],
"active_id": ref("payment_order_0"), })
-
I create payment lines entries.
-
!python {model: payment.order.create}: |
invoice = self.pool.get('account.invoice').browse(cr, uid, ref("account_invoice_supplier0"))
entries = []
for move_line in invoice.move_id.line_id:
if move_line.credit and not move_line.debit:
entries.append((6, 0, [move_line.id]))
self.write(cr, uid, [ref("payment_order_create_0")], {'entries': entries})
self.create_payment(cr, uid, [ref("payment_order_create_0")], {
"active_model": "payment.order", "active_ids": [ref("payment_order_0")],
"active_id": ref("payment_order_0")})
pay_obj = self.pool.get('payment.order')
pay = pay_obj.browse(cr, uid, ref('payment_order_0'))
for line in pay.line_ids:
assert line.amount != 0.0
-
I confirm the payment order.
-
!workflow {model: payment.order, action: open, ref: payment_order_0}
-
I check that payment order is now "Confirmed".
-
!assert {model: payment.order, id: payment_order_0, severity: error, string: Payment Order should be 'Confirmed'.}:
- state == 'open'
-
I create the wizard for paying the payment
-
!record {model: payment.manual, id: payment_manual_0}:
create_date: !eval time.strftime('%Y-%m-%d')
-
I click OK
-
!python {model: payment.manual}: |
if context is None:
context = {}
context.update({'active_ids': [ref("payment_order_0")]})
self.button_ok(cr, uid, ref("payment_manual_0"), context)
-
I check that the payment order is now "Sent".
-
!assert {model: payment.order, id: payment_order_0, severity: error, string: Payment Order should be 'Sent'.}:
- state == 'sent'
-
I check that the invoice has payments associated
-
!assert {model: account.invoice, id: account_invoice_supplier0, severity: error, string: payment_ids should be populated}:
- payment_ids
-
I check the content of the payment of the invoice
-
!python {model: account.invoice}: |
inv = self.browse(cr, uid, ref("account_invoice_supplier0"))
assert round(inv.payment_ids[0].debit, 2) == 1005.55
assert inv.payment_ids[0].credit == 0
assert inv.payment_ids[0].reconcile_id.id != False
assert inv.payment_ids[0].reconcile_ref != False
assert inv.state == 'paid'
-
I create the bank statement to reconcile the transfer account move
-
!record {model: account.bank.statement, id: bank_statement_0}:
name: BK test
balance_end_real: 0.0
balance_start: 0.0
date: !eval time.strftime('%Y-%m-%d')
journal_id: account.bank_journal
-
I create bank statement line
-
!python {model: account.bank.statement.line}: |
vals = {
'amount': -1005.55,
'partner_id': ref('base.res_partner_4'),
'statement_id': ref('bank_statement_0'),
'name': 'Pay invoice',
'journal_id': ref("account.bank_journal"),
}
line_id = self.create(cr, uid, vals)
assert line_id, "Account bank statement line has not been created"
-
I reconcile the move transfer (not the invoice) with the payment.
-
!python {model: account.bank.statement}: |
inv_obj = self.pool.get('account.invoice')
statement_obj = self.pool.get('account.bank.statement.line')
transfer_entry = inv_obj.browse(cr, uid, ref("account_invoice_supplier0")).payment_ids[0].move_id
for line in transfer_entry.line_id:
if not line.reconcile_id and line.credit:
counterpart_move_line = line
break
browse_payment = self.browse(cr, uid, ref("bank_statement_0"))
for line in browse_payment.line_ids:
statement_obj.process_reconciliation(cr, uid, line.id, [{
'counterpart_move_line_id': counterpart_move_line.id,
'credit':0,
'debit': counterpart_move_line.credit,
'name': line.name,
}])
self.write(cr, uid, ref("bank_statement_0"), {'balance_end_real': -1005.55})
self.button_confirm_bank(cr, uid, ref("bank_statement_0"))
-
I check that the bank statement is confirm
-
!assert {model: account.bank.statement, id: bank_statement_0, severity: error, string: Bank Statement should be confirm}:
- state == 'confirm'
-
I check that the payment is done
-
!assert {model: payment.order, id: payment_order_0, severity: error, string: Payment Order should be done}:
- state == 'done'

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_payment_order_form_inherit" model="ir.ui.view">
<field name="name">account.payment.order.form (account_banking_payment_transfer)</field>
<field name="model">payment.order</field>
<field name="inherit_id" ref="account_payment.view_payment_order_form" />
<field name="arch" type="xml">
<xpath expr="//button[@name='%(account_payment.action_create_payment_order)d']" position="after">
<button class="oe_inline oe_stat_button"
name="partial" type="object"
attrs="{'invisible':[('partial_reconcile_count','=',0)]}"
icon="fa-check-square-o">
<field name="partial_reconcile_count" widget="statinfo"/>
</button>
</xpath>
</field>
</record>
</data>
</openerp>