mirror of
https://github.com/OCA/bank-payment.git
synced 2025-02-02 10:37:31 +02:00
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:
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
28
account_banking_payment_transfer/test/data.yml
Normal file
28
account_banking_payment_transfer/test/data.yml
Normal 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
|
||||
@@ -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'
|
||||
@@ -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'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
164
account_banking_payment_transfer/test/test_payment_method.yml
Normal file
164
account_banking_payment_transfer/test/test_payment_method.yml
Normal 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'
|
||||
20
account_banking_payment_transfer/view/account_payment.xml
Normal file
20
account_banking_payment_transfer/view/account_payment.xml
Normal 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>
|
||||
Reference in New Issue
Block a user