[FIX] account: Fix available_partner_bank_ids in payment & register payment wizard

When paying an expense, we should take the bank accounts from partner instead of the ones set on the company.
Also, the computation of the partner bank account is different on the wizard and the payment.
To unify both models, the 'available_partner_bank_ids' should also be put on account.payment.

see https://github.com/odoo/odoo/pull/79737

closes odoo/odoo#81236

X-original-commit: 89d8c0a1ea03919e9f3085ea0d05aa80ad443ff7
Signed-off-by: Olivier Colson <oco@odoo.com>
Signed-off-by: Laurent Smet <las@odoo.com>
This commit is contained in:
Laurent Smet
2021-12-10 16:24:45 +00:00
parent ed75307baf
commit 26a5d7d299
5 changed files with 260 additions and 104 deletions
+16 -14
View File
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.exceptions import UserError, ValidationError
@@ -33,10 +32,14 @@ class AccountPayment(models.Model):
is_matched = fields.Boolean(string="Is Matched With a Bank Statement", store=True,
compute='_compute_reconciliation_status',
help="Technical field indicating if the payment has been matched with a statement line.")
available_partner_bank_ids = fields.Many2many(
comodel_name='res.partner.bank',
compute='_compute_available_partner_bank_ids',
)
partner_bank_id = fields.Many2one('res.partner.bank', string="Recipient Bank Account",
readonly=False, store=True, tracking=True,
compute='_compute_partner_bank_id',
domain="[('partner_id', '=', partner_id)]",
domain="[('id', 'in', available_partner_bank_ids)]",
check_company=True)
is_internal_transfer = fields.Boolean(string="Internal Transfer",
readonly=False, store=True,
@@ -369,21 +372,20 @@ class AccountPayment(models.Model):
else:
payment.amount_signed = payment.amount
@api.depends('partner_id', 'company_id', 'payment_type', 'destination_journal_id', 'is_internal_transfer')
@api.depends('partner_id', 'company_id', 'payment_type')
def _compute_available_partner_bank_ids(self):
for pay in self:
if pay.payment_type == 'inbound':
pay.available_partner_bank_ids = pay.journal_id.bank_account_id
else:
pay.available_partner_bank_ids = pay.partner_id.bank_ids\
.filtered(lambda x: x.company_id.id in (False, pay.company_id.id))._origin
@api.depends('available_partner_bank_ids', 'journal_id')
def _compute_partner_bank_id(self):
''' The default partner_bank_id will be the first available on the partner. '''
for pay in self:
if pay.payment_type == 'inbound':
bank_partner = pay.company_id.partner_id
else:
bank_partner = pay.partner_id
available_partner_bank_accounts = bank_partner.bank_ids.filtered(lambda x: x.company_id.id in (False, pay.company_id.id))
if available_partner_bank_accounts:
if pay.partner_bank_id not in available_partner_bank_accounts:
pay.partner_bank_id = available_partner_bank_accounts[0]._origin
else:
pay.partner_bank_id = False
pay.partner_bank_id = pay.available_partner_bank_ids[:1]._origin
@api.depends('partner_id', 'destination_account_id', 'journal_id')
def _compute_is_internal_transfer(self):
+35 -10
View File
@@ -11,10 +11,13 @@ class TestAccountPayment(AccountTestInvoicingCommon):
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
company_id = cls.company_data['default_journal_bank'].company_id
company = cls.company_data['default_journal_bank'].company_id
cls.payment_debit_account_id = cls.copy_account(company_id.account_journal_payment_debit_account_id)
cls.payment_credit_account_id = cls.copy_account(company_id.account_journal_payment_credit_account_id)
cls.payment_debit_account_id = cls.copy_account(company.account_journal_payment_debit_account_id)
cls.payment_credit_account_id = cls.copy_account(company.account_journal_payment_credit_account_id)
cls.bank_journal_1 = cls.company_data['default_journal_bank']
cls.bank_journal_2 = cls.company_data['default_journal_bank'].copy()
cls.partner_bank_account1 = cls.env['res.partner.bank'].create({
'acc_number': "0123456789",
@@ -26,13 +29,18 @@ class TestAccountPayment(AccountTestInvoicingCommon):
'partner_id': cls.partner_a.id,
'acc_type': 'bank',
})
cls.comp_bank_account = cls.env['res.partner.bank'].create({
cls.comp_bank_account1 = cls.env['res.partner.bank'].create({
'acc_number': "985632147",
'partner_id': cls.env.company.partner_id.id,
'acc_type': 'bank',
})
cls.comp_bank_account2 = cls.env['res.partner.bank'].create({
'acc_number': "741258963",
'partner_id': cls.env.company.partner_id.id,
'acc_type': 'bank',
})
company_id.write({
company.write({
'account_journal_payment_debit_account_id': cls.payment_debit_account_id.id,
'account_journal_payment_credit_account_id': cls.payment_credit_account_id.id
})
@@ -813,17 +821,34 @@ class TestAccountPayment(AccountTestInvoicingCommon):
'payment_method_line_id': self.outbound_payment_method_line.id,
}])
def test_payment_partner_bank_inbound(self):
""" Test the bank account is well recomputed for inbound payments. In that case, the recipient
bank account must be the one set on the company.
def test_suggested_default_partner_bank(self):
""" Ensure the 'partner_bank_id' is well computed on payments. When the payment is inbound, the money must be
received by a bank account linked to the company. In case of outbound payment, the bank account must be found
on the partner.
"""
payment = self.env['account.payment'].create({
'journal_id': self.bank_journal_1.id,
'amount': 50.0,
'payment_type': 'outbound',
'partner_type': 'supplier',
'partner_id': self.partner_a.id,
})
self.assertRecordValues(payment, [{'partner_bank_id': self.partner_bank_account1.id}])
self.assertRecordValues(payment, [{
'available_partner_bank_ids': self.partner_a.bank_ids.ids,
'partner_bank_id': self.partner_bank_account1.id,
}])
payment.payment_type = 'inbound'
self.assertRecordValues(payment, [{'partner_bank_id': self.comp_bank_account.id}])
self.assertRecordValues(payment, [{
'available_partner_bank_ids': [],
'partner_bank_id': False,
}])
self.bank_journal_2.bank_account_id = self.comp_bank_account2
# A sequence is automatically added on the first move. We need to clean it before changing the journal.
payment.name = False
payment.journal_id = self.bank_journal_2
self.assertRecordValues(payment, [{
'available_partner_bank_ids': self.comp_bank_account2.ids,
'partner_bank_id': self.comp_bank_account2.id,
}])
@@ -21,11 +21,19 @@ class TestAccountPaymentRegister(AccountTestInvoicingCommon):
cls.payment_debit_account_id = cls.company_data['default_journal_bank'].company_id.account_journal_payment_debit_account_id.copy()
cls.payment_credit_account_id = cls.company_data['default_journal_bank'].company_id.account_journal_payment_credit_account_id.copy()
cls.partner_bank_account = cls.env['res.partner.bank'].create({
cls.bank_journal_1 = cls.company_data['default_journal_bank']
cls.bank_journal_2 = cls.company_data['default_journal_bank'].copy()
cls.partner_bank_account1 = cls.env['res.partner.bank'].create({
'acc_number': "0123456789",
'partner_id': cls.partner_a.id,
'acc_type': 'bank',
})
cls.partner_bank_account2 = cls.env['res.partner.bank'].create({
'acc_number': "9876543210",
'partner_id': cls.partner_a.id,
'acc_type': 'bank',
})
cls.comp_bank_account1 = cls.env['res.partner.bank'].create({
'acc_number': "985632147",
'partner_id': cls.env.company.partner_id.id,
@@ -575,6 +583,9 @@ class TestAccountPaymentRegister(AccountTestInvoicingCommon):
''' Choose to pay multiple batches, one with two customer invoices (1000 + 2000)
and one with a vendor bill of 600, by splitting payments.
'''
self.in_invoice_1.partner_bank_id = self.partner_bank_account1
self.in_invoice_2.partner_bank_id = self.partner_bank_account2
active_ids = (self.in_invoice_1 + self.in_invoice_2 + self.in_invoice_3).ids
payments = self.env['account.payment.register'].with_context(active_model='account.move', active_ids=active_ids).create({
'group_payment': False,
@@ -582,16 +593,22 @@ class TestAccountPaymentRegister(AccountTestInvoicingCommon):
self.assertRecordValues(payments, [
{
'journal_id': self.bank_journal_1.id,
'ref': 'BILL/2017/01/0001',
'payment_method_line_id': self.outbound_payment_method_line.id,
'partner_bank_id': self.partner_bank_account1.id,
},
{
'journal_id': self.bank_journal_1.id,
'ref': 'BILL/2017/01/0002',
'payment_method_line_id': self.outbound_payment_method_line.id,
'partner_bank_id': self.partner_bank_account2.id,
},
{
'journal_id': self.bank_journal_1.id,
'ref': 'BILL/2017/01/0003',
'payment_method_line_id': self.outbound_payment_method_line.id,
'partner_bank_id': False,
},
])
self.assertRecordValues(payments[0].line_ids.sorted('balance') + payments[1].line_ids.sorted('balance') + payments[2].line_ids.sorted('balance'), [
@@ -648,27 +665,6 @@ class TestAccountPaymentRegister(AccountTestInvoicingCommon):
},
])
def test_register_payment_custom_bank_account(self):
""" Ensure the user is able to select a custom bank account when registering a payment and this bank account
lands correctly on the generated payment.
"""
self.out_invoice_1.partner_bank_id = self.comp_bank_account1
ctx = {'active_model': 'account.move', 'active_ids': self.out_invoice_1.ids}
wizard_form = Form(self.env['account.payment.register'].with_context(**ctx))
wizard = wizard_form.save()
# The bank account set on the invoice must be the default suggested value.
self.assertRecordValues(wizard, [{'partner_bank_id': self.comp_bank_account1.id}])
wizard_form = Form(wizard)
wizard_form.partner_bank_id = self.comp_bank_account2
wizard = wizard_form.save()
payments = wizard._create_payments()
# The user should be able to set a custom bank account.
self.assertRecordValues(payments, [{'partner_bank_id': self.comp_bank_account2.id}])
def test_register_payment_constraints(self):
# Test to register a payment for a draft journal entry.
self.out_invoice_1.button_draft()
@@ -933,3 +929,80 @@ class TestAccountPaymentRegister(AccountTestInvoicingCommon):
'reconciled': False,
},
])
def test_suggested_default_partner_bank_inbound_payment(self):
""" Test the suggested bank account on the wizard for inbound payment. """
self.out_invoice_1.partner_bank_id = False
ctx = {'active_model': 'account.move', 'active_ids': self.out_invoice_1.ids}
wizard = self.env['account.payment.register'].with_context(**ctx).create({})
self.assertRecordValues(wizard, [{
'journal_id': self.bank_journal_1.id,
'available_partner_bank_ids': [],
'partner_bank_id': False,
}])
self.bank_journal_2.bank_account_id = self.out_invoice_1.partner_bank_id = self.comp_bank_account2
wizard = self.env['account.payment.register'].with_context(**ctx).create({})
self.assertRecordValues(wizard, [{
'journal_id': self.bank_journal_2.id,
'available_partner_bank_ids': self.comp_bank_account2.ids,
'partner_bank_id': self.comp_bank_account2.id,
}])
wizard.journal_id = self.bank_journal_1
self.assertRecordValues(wizard, [{
'journal_id': self.bank_journal_1.id,
'available_partner_bank_ids': [],
'partner_bank_id': False,
}])
def test_suggested_default_partner_bank_outbound_payment(self):
""" Test the suggested bank account on the wizard for outbound payment. """
self.in_invoice_1.partner_bank_id = False
ctx = {'active_model': 'account.move', 'active_ids': self.in_invoice_1.ids}
wizard = self.env['account.payment.register'].with_context(**ctx).create({})
self.assertRecordValues(wizard, [{
'journal_id': self.bank_journal_1.id,
'available_partner_bank_ids': self.partner_a.bank_ids.ids,
'partner_bank_id': self.partner_bank_account1.id,
}])
self.in_invoice_1.partner_bank_id = self.partner_bank_account2
wizard = self.env['account.payment.register'].with_context(**ctx).create({})
self.assertRecordValues(wizard, [{
'journal_id': self.bank_journal_1.id,
'available_partner_bank_ids': self.partner_a.bank_ids.ids,
'partner_bank_id': self.partner_bank_account2.id,
}])
wizard.journal_id = self.bank_journal_2
self.assertRecordValues(wizard, [{
'journal_id': self.bank_journal_2.id,
'available_partner_bank_ids': self.partner_a.bank_ids.ids,
'partner_bank_id': self.partner_bank_account2.id,
}])
def test_register_payment_inbound_multiple_bank_account(self):
""" Pay customer invoices with different bank accounts. """
self.out_invoice_1.partner_bank_id = self.comp_bank_account1
self.out_invoice_2.partner_bank_id = self.comp_bank_account2
self.bank_journal_2.bank_account_id = self.comp_bank_account2
ctx = {'active_model': 'account.move', 'active_ids': (self.out_invoice_1 + self.out_invoice_2).ids}
wizard = self.env['account.payment.register'].with_context(**ctx).create({'journal_id': self.bank_journal_2.id})
payments = wizard._create_payments()
self.assertRecordValues(payments, [
{
'journal_id': self.bank_journal_2.id,
'ref': 'INV/2017/00001',
'partner_bank_id': self.comp_bank_account2.id,
},
{
'journal_id': self.bank_journal_2.id,
'ref': 'INV/2017/00002',
'partner_bank_id': self.comp_bank_account2.id,
},
])
@@ -178,6 +178,7 @@
<field name="show_partner_bank_account" invisible="1"/>
<field name="require_partner_bank_account" invisible="1"/>
<field name="available_payment_method_line_ids" invisible="1"/>
<field name="available_partner_bank_ids" invisible="1"/>
<field name="suitable_journal_ids" invisible="1"/>
<field name="country_code" invisible="1"/>
<field name="partner_type" invisible="1"/>
+113 -58
View File
@@ -129,6 +129,73 @@ class AccountPaymentRegister(models.TransientModel):
labels = set(line.name or line.move_id.ref or line.move_id.name for line in batch_result['lines'])
return ' '.join(sorted(labels))
@api.model
def _get_batch_available_journals(self, batch_result):
""" Helper to compute the available journals based on the batch.
:param batch_result: A batch returned by '_get_batches'.
:return: A recordset of account.journal.
"""
payment_type = batch_result['payment_values']['payment_type']
company = batch_result['lines'].company_id
journals = self.env['account.journal'].search([('company_id', '=', company.id), ('type', 'in', ('bank', 'cash'))])
if payment_type == 'inbound':
return journals.filtered('inbound_payment_method_line_ids')
else:
return journals.filtered('outbound_payment_method_line_ids')
@api.model
def _get_batch_journal(self, batch_result):
""" Helper to compute the journal based on the batch.
:param batch_result: A batch returned by '_get_batches'.
:return: An account.journal record.
"""
payment_values = batch_result['payment_values']
foreign_currency_id = payment_values['currency_id']
partner_bank_id = payment_values['partner_bank_id']
currency_domain = [('currency_id', '=', foreign_currency_id)]
partner_bank_domain = [('bank_account_id', '=', partner_bank_id)]
default_domain = [
('type', 'in', ('bank', 'cash')),
('company_id', '=', batch_result['lines'].company_id.id),
]
if partner_bank_id:
extra_domains = (
currency_domain + partner_bank_domain,
partner_bank_domain,
currency_domain,
[],
)
else:
extra_domains = (
currency_domain,
[],
)
for extra_domain in extra_domains:
journal = self.env['account.journal'].search(default_domain + extra_domain, limit=1)
if journal:
return journal
return self.env['account.journal']
@api.model
def _get_batch_available_partner_banks(self, batch_result, journal):
payment_values = batch_result['payment_values']
company = batch_result['lines'].company_id
# A specific bank account is set on the journal. The user must use this one.
if payment_values['payment_type'] == 'inbound':
# Receiving money on a bank account linked to the journal.
return journal.bank_account_id
else:
# Sending money to a bank account owned by a partner.
return batch_result['lines'].partner_id.bank_ids.filtered(lambda x: x.company_id.id in (False, company.id))._origin
@api.model
def _get_line_batch_key(self, line):
''' Turn the line passed as parameter to a dictionary defining on which way the lines
@@ -136,15 +203,15 @@ class AccountPaymentRegister(models.TransientModel):
:return: A python dictionary.
'''
move = line.move_id
partner_bank_account = self.env['res.partner.bank']
partner_bank_account = self.env['res.partner.bank']
if move.is_invoice(include_receipts=True):
partner_bank_account = move.partner_bank_id._origin
return {
'partner_id': line.partner_id.id,
'account_id': line.account_id.id,
'currency_id': (line.currency_id or line.company_currency_id).id,
'currency_id': line.currency_id.id,
'partner_bank_id': partner_bank_account.id,
'partner_type': 'customer' if line.account_internal_type == 'receivable' else 'supplier',
}
@@ -234,7 +301,6 @@ class AccountPaymentRegister(models.TransientModel):
'partner_id': False,
'partner_type': False,
'payment_type': wizard_values_from_batch['payment_type'],
'partner_bank_id': False,
'source_currency_id': False,
'source_amount': False,
'source_amount_currency': False,
@@ -263,74 +329,57 @@ class AccountPaymentRegister(models.TransientModel):
else:
wizard.group_payment = False
@api.depends('company_id', 'source_currency_id', 'line_ids')
def _compute_journal_id(self):
for wizard in self:
if wizard.journal_id:
wizard.journal_id = wizard.journal_id
else:
partner_bank_id = wizard.line_ids.move_id.mapped('partner_bank_id')
company_domain = [('company_id', '=', wizard.company_id.id), ('id', 'in', wizard.available_journal_ids.ids)]
bank_domain = [('bank_account_id', '=', partner_bank_id.id), ('type', '=', 'bank')] if len(partner_bank_id) == 1 else None
no_bank_domain = [('type', 'in', ('bank', 'cash'))]
journal = None
if wizard.source_currency_id:
currency_domain = [('currency_id', '=', wizard.source_currency_id.id)]
if bank_domain:
journal = self.env['account.journal'].search(company_domain + currency_domain + bank_domain, limit=1)
if not journal:
journal = self.env['account.journal'].search(company_domain + currency_domain + no_bank_domain, limit=1)
if not journal and bank_domain:
journal = self.env['account.journal'].search(company_domain + bank_domain, limit=1)
if not journal:
journal = self.env['account.journal'].search(company_domain + no_bank_domain, limit=1)
wizard.journal_id = journal
@api.depends('payment_type')
def _compute_available_journal_ids(self):
"""
Get all journals having at least one payment method for inbound/outbound depending on the payment_type.
"""
journals = self.env['account.journal'].search([
('company_id', 'in', self.company_id.ids), ('type', 'in', ('bank', 'cash'))
])
for pay in self:
if pay.payment_type == 'inbound':
pay.available_journal_ids = journals.filtered(
lambda j: j.company_id == pay.company_id and j.inbound_payment_method_line_ids.ids != []
)
else:
pay.available_journal_ids = journals.filtered(
lambda j: j.company_id == pay.company_id and j.outbound_payment_method_line_ids.ids != []
)
@api.depends('journal_id')
def _compute_currency_id(self):
for wizard in self:
wizard.currency_id = wizard.journal_id.currency_id or wizard.source_currency_id or wizard.company_id.currency_id
@api.depends('company_id', 'can_edit_wizard')
@api.depends('payment_type', 'company_id', 'can_edit_wizard')
def _compute_available_journal_ids(self):
for wizard in self:
if wizard.can_edit_wizard:
batch = wizard._get_batches()[0]
wizard.available_journal_ids = wizard._get_batch_available_journals(batch)
else:
wizard.available_journal_ids = self.env['account.journal'].search([
('company_id', '=', wizard.company_id.id),
('type', 'in', ('bank', 'cash')),
])
@api.depends('available_journal_ids')
def _compute_journal_id(self):
for wizard in self:
if wizard.can_edit_wizard:
batch = wizard._get_batches()[0]
wizard.journal_id = wizard._get_batch_journal(batch)
else:
wizard.journal_id = self.env['account.journal'].search([
('type', 'in', ('bank', 'cash')),
('company_id', '=', wizard.company_id.id),
], limit=1)
@api.depends('can_edit_wizard', 'journal_id')
def _compute_available_partner_bank_ids(self):
for wizard in self:
if wizard.can_edit_wizard:
batches = wizard._get_batches()
bank_partners = batches[0]['lines'].move_id.bank_partner_id
wizard.available_partner_bank_ids = bank_partners.bank_ids\
.filtered(lambda x: x.company_id.id in (False, wizard.company_id.id))._origin
batch = wizard._get_batches()[0]
wizard.available_partner_bank_ids = wizard._get_batch_available_partner_banks(batch, wizard.journal_id)
else:
wizard.available_partner_bank_ids = False
wizard.available_partner_bank_ids = None
@api.depends('available_partner_bank_ids')
@api.depends('journal_id', 'available_partner_bank_ids')
def _compute_partner_bank_id(self):
for wizard in self:
if wizard.can_edit_wizard:
batches = wizard._get_batches()
wizard.partner_bank_id = self.env['res.partner.bank'].browse(batches[0]['payment_values']['partner_bank_id'])
batch = wizard._get_batches()[0]
partner_bank_id = batch['payment_values']['partner_bank_id']
available_partner_banks = wizard.available_partner_bank_ids._origin
if partner_bank_id and partner_bank_id in available_partner_banks.ids:
wizard.partner_bank_id = self.env['res.partner.bank'].browse(partner_bank_id)
else:
wizard.partner_bank_id = available_partner_banks[:1]
else:
wizard.partner_bank_id = False
wizard.partner_bank_id = None
@api.depends('payment_type', 'journal_id')
def _compute_payment_method_line_fields(self):
@@ -469,6 +518,12 @@ class AccountPaymentRegister(models.TransientModel):
def _create_payment_vals_from_batch(self, batch_result):
batch_values = self._get_wizard_values_from_batch(batch_result)
if batch_values['payment_type'] == 'inbound':
partner_bank_id = self.journal_id.bank_account_id.id
else:
partner_bank_id = batch_result['payment_values']['partner_bank_id']
return {
'date': self.payment_date,
'amount': batch_values['source_amount_currency'],
@@ -478,7 +533,7 @@ class AccountPaymentRegister(models.TransientModel):
'journal_id': self.journal_id.id,
'currency_id': batch_values['source_currency_id'],
'partner_id': batch_values['partner_id'],
'partner_bank_id': batch_result['payment_values']['partner_bank_id'],
'partner_bank_id': partner_bank_id,
'payment_method_line_id': self.payment_method_line_id.id,
'destination_account_id': batch_result['lines'][0].account_id.id
}