[FIX] purchase : Account analytic default changed by user is reset in invoice

Reproduce :
1. Add a default analytical rule: if Coin Gourmand partner then Administrative analytical account
2. Create a PO at the Coin Gourmand supplier, add a product, and select the Internal analytical account.
3. Bill for this product.

Result :
Internal analytical account has disappeared from the invoice line. It has been replaced by Administrative.

Issue :
AccountMove.create() computes an account in the move and this causes the analytic account to be reset at its default value from the rule.

Fix :
Since the account pocalypse in 13.0, account.move is making magic things during the creation of the invoice to create dynamically the invoice lines like account.invoice did before this huge refactoring.
The create is making a 'New' record to simulate the onchange but this code is a hack triggering unexpected recomputation like the analytic account.
To avoid that, we ensure to assign the minimum number of fields to preserve fields like the analytic account.

closes odoo/odoo#79321

X-original-commit: 876e2d1073312fe65cb6b2e3038235aeaa082c8a
Related: odoo/enterprise#22074
Signed-off-by: Laurent Smet <las@openerp.com>
Signed-off-by: Josse Colpaert <jco@openerp.com>
This commit is contained in:
Audric Onockx (auon)
2021-11-03 13:26:55 +00:00
committed by Laurent Smet
parent bd8260b3f5
commit 5c09a8d9f7
6 changed files with 224 additions and 60 deletions
+15 -5
View File
@@ -2074,10 +2074,10 @@ class AccountMove(models.Model):
'''
self.ensure_one()
for line in self.line_ids:
# Do something only on invoice lines.
if line.exclude_from_invoice_tab:
continue
invoice_lines = self.line_ids.filtered(lambda x: not x.exclude_from_invoice_tab and not x.display_type and not x._origin)
cached_vals_list = [dict(x._cache) for x in invoice_lines]
for cached_vals, line in zip(cached_vals_list, invoice_lines):
# Shortcut to load the demo data.
# Doing line.account_id triggers a default_get(['account_id']) that could returns a result.
@@ -2096,10 +2096,15 @@ class AccountMove(models.Model):
# Ensure related fields are well copied.
if line.partner_id != self.partner_id.commercial_partner_id:
line.partner_id = self.partner_id.commercial_partner_id
line.date = self.date
line.recompute_tax_line = True
line.currency_id = self.currency_id
for k, v in cached_vals.items():
field = line._fields[k]
if field.compute and not field.readonly:
line[k] = v
self.line_ids._onchange_price_subtotal()
self._recompute_dynamic_lines(recompute_all_taxes=True)
@@ -2174,7 +2179,12 @@ class AccountMove(models.Model):
vals['line_ids'] = vals.pop('invoice_line_ids')
for invoice in self:
invoice_new = invoice.with_context(default_move_type=invoice.move_type, default_journal_id=invoice.journal_id.id).new(origin=invoice)
invoice_new = invoice.with_context(
default_move_type=invoice.move_type,
default_journal_id=invoice.journal_id.id,
default_partner_id=invoice.partner_id.id,
default_currency_id=invoice.currency_id.id,
).new(origin=invoice)
invoice_new.update(vals)
values = invoice_new._move_autocomplete_invoice_lines_values()
values.pop('invoice_line_ids', None)
@@ -4,7 +4,7 @@ from freezegun import freeze_time
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.tests.common import Form
from odoo.tests import tagged
from odoo import fields
from odoo import fields, Command
from odoo.exceptions import UserError, ValidationError
@@ -1396,9 +1396,19 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon):
'currency_id': self.currency_data['currency'].id,
'invoice_payment_term_id': self.pay_terms_a.id,
'invoice_line_ids': [
(0, None, self.product_line_vals_1),
(0, None, self.product_line_vals_2),
]
Command.create({
'product_id': self.product_line_vals_1['product_id'],
'product_uom_id': self.product_line_vals_1['product_uom_id'],
'price_unit': self.product_line_vals_1['price_unit'],
'tax_ids': [Command.set(self.product_line_vals_1['tax_ids'])],
}),
Command.create({
'product_id': self.product_line_vals_2['product_id'],
'product_uom_id': self.product_line_vals_2['product_uom_id'],
'price_unit': self.product_line_vals_2['price_unit'],
'tax_ids': [Command.set(self.product_line_vals_2['tax_ids'])],
}),
],
})
self.assertInvoiceValues(move, [
@@ -1446,13 +1456,23 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon):
'currency_id': self.currency_data['currency'].id,
'invoice_payment_term_id': self.pay_terms_a.id,
'invoice_line_ids': [
(0, None, self.product_line_vals_1),
]
Command.create({
'product_id': self.product_line_vals_1['product_id'],
'product_uom_id': self.product_line_vals_1['product_uom_id'],
'price_unit': self.product_line_vals_1['price_unit'],
'tax_ids': [Command.set(self.product_line_vals_1['tax_ids'])],
}),
],
})
move.write({
'invoice_line_ids': [
(0, None, self.product_line_vals_2),
]
Command.create({
'product_id': self.product_line_vals_2['product_id'],
'product_uom_id': self.product_line_vals_2['product_uom_id'],
'price_unit': self.product_line_vals_2['price_unit'],
'tax_ids': [Command.set(self.product_line_vals_2['tax_ids'])],
}),
],
})
self.assertInvoiceValues(move, [
@@ -1508,9 +1528,19 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon):
'currency_id': self.currency_data['currency'].id,
'invoice_payment_term_id': self.pay_terms_a.id,
'invoice_line_ids': [
(0, None, self.product_line_vals_1),
(0, None, self.product_line_vals_2),
]
Command.create({
'product_id': self.product_line_vals_1['product_id'],
'product_uom_id': self.product_line_vals_1['product_uom_id'],
'price_unit': self.product_line_vals_1['price_unit'],
'tax_ids': [Command.set(self.product_line_vals_1['tax_ids'])],
}),
Command.create({
'product_id': self.product_line_vals_2['product_id'],
'product_uom_id': self.product_line_vals_2['product_uom_id'],
'price_unit': self.product_line_vals_2['price_unit'],
'tax_ids': [Command.set(self.product_line_vals_2['tax_ids'])],
}),
],
})
move.action_switch_invoice_into_refund_credit_note()
@@ -1558,10 +1588,6 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon):
def test_in_invoice_switch_in_refund_2(self):
# Test creating an account_move with an in_invoice_type and switch it in an in_refund and a negative quantity.
modified_product_line_vals_1 = self.product_line_vals_1.copy()
modified_product_line_vals_1.update({'quantity': -modified_product_line_vals_1['quantity']})
modified_product_line_vals_2 = self.product_line_vals_2.copy()
modified_product_line_vals_2.update({'quantity': -modified_product_line_vals_2['quantity']})
move = self.env['account.move'].create({
'move_type': 'in_invoice',
'partner_id': self.partner_a.id,
@@ -1569,9 +1595,21 @@ class TestAccountMoveInInvoiceOnchanges(AccountTestInvoicingCommon):
'currency_id': self.currency_data['currency'].id,
'invoice_payment_term_id': self.pay_terms_a.id,
'invoice_line_ids': [
(0, None, modified_product_line_vals_1),
(0, None, modified_product_line_vals_2),
]
Command.create({
'product_id': self.product_line_vals_1['product_id'],
'product_uom_id': self.product_line_vals_1['product_uom_id'],
'price_unit': self.product_line_vals_1['price_unit'],
'quantity': -self.product_line_vals_1['quantity'],
'tax_ids': [Command.set(self.product_line_vals_1['tax_ids'])],
}),
Command.create({
'product_id': self.product_line_vals_2['product_id'],
'product_uom_id': self.product_line_vals_2['product_uom_id'],
'price_unit': self.product_line_vals_2['price_unit'],
'quantity': -self.product_line_vals_2['quantity'],
'tax_ids': [Command.set(self.product_line_vals_2['tax_ids'])],
}),
],
})
self.assertInvoiceValues(move, [
@@ -3,7 +3,7 @@
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.tests.common import Form
from odoo.tests import tagged
from odoo import fields
from odoo import fields, Command
@tagged('post_install', '-at_install')
@@ -844,9 +844,19 @@ class TestAccountMoveInRefundOnchanges(AccountTestInvoicingCommon):
'currency_id': self.currency_data['currency'].id,
'invoice_payment_term_id': self.pay_terms_a.id,
'invoice_line_ids': [
(0, None, self.product_line_vals_1),
(0, None, self.product_line_vals_2),
]
Command.create({
'product_id': self.product_line_vals_1['product_id'],
'product_uom_id': self.product_line_vals_1['product_uom_id'],
'price_unit': self.product_line_vals_1['price_unit'],
'tax_ids': [Command.set(self.product_line_vals_1['tax_ids'])],
}),
Command.create({
'product_id': self.product_line_vals_2['product_id'],
'product_uom_id': self.product_line_vals_2['product_uom_id'],
'price_unit': self.product_line_vals_2['price_unit'],
'tax_ids': [Command.set(self.product_line_vals_2['tax_ids'])],
}),
],
})
self.assertInvoiceValues(move, [
@@ -894,13 +904,23 @@ class TestAccountMoveInRefundOnchanges(AccountTestInvoicingCommon):
'currency_id': self.currency_data['currency'].id,
'invoice_payment_term_id': self.pay_terms_a.id,
'invoice_line_ids': [
(0, None, self.product_line_vals_1),
]
Command.create({
'product_id': self.product_line_vals_1['product_id'],
'product_uom_id': self.product_line_vals_1['product_uom_id'],
'price_unit': self.product_line_vals_1['price_unit'],
'tax_ids': [Command.set(self.product_line_vals_1['tax_ids'])],
}),
],
})
move.write({
'invoice_line_ids': [
(0, None, self.product_line_vals_2),
]
Command.create({
'product_id': self.product_line_vals_2['product_id'],
'product_uom_id': self.product_line_vals_2['product_uom_id'],
'price_unit': self.product_line_vals_2['price_unit'],
'tax_ids': [Command.set(self.product_line_vals_2['tax_ids'])],
}),
],
})
self.assertInvoiceValues(move, [
@@ -3,7 +3,7 @@
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.tests.common import Form
from odoo.tests import tagged
from odoo import fields
from odoo import fields, Command
from odoo.exceptions import UserError
from unittest.mock import patch
@@ -2066,9 +2066,19 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
'currency_id': self.currency_data['currency'].id,
'invoice_payment_term_id': self.pay_terms_a.id,
'invoice_line_ids': [
(0, None, self.product_line_vals_1),
(0, None, self.product_line_vals_2),
]
Command.create({
'product_id': self.product_line_vals_1['product_id'],
'product_uom_id': self.product_line_vals_1['product_uom_id'],
'price_unit': self.product_line_vals_1['price_unit'],
'tax_ids': [Command.set(self.product_line_vals_1['tax_ids'])],
}),
Command.create({
'product_id': self.product_line_vals_2['product_id'],
'product_uom_id': self.product_line_vals_2['product_uom_id'],
'price_unit': self.product_line_vals_2['price_unit'],
'tax_ids': [Command.set(self.product_line_vals_2['tax_ids'])],
}),
],
})
self.assertEqual(partner_a_child.id, move.partner_id.id, 'Keep child partner on the account move record')
@@ -2083,13 +2093,23 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
'currency_id': self.currency_data['currency'].id,
'invoice_payment_term_id': self.pay_terms_a.id,
'invoice_line_ids': [
(0, None, self.product_line_vals_1),
]
Command.create({
'product_id': self.product_line_vals_1['product_id'],
'product_uom_id': self.product_line_vals_1['product_uom_id'],
'price_unit': self.product_line_vals_1['price_unit'],
'tax_ids': [Command.set(self.product_line_vals_1['tax_ids'])],
}),
],
})
move.write({
'invoice_line_ids': [
(0, None, self.product_line_vals_2),
]
Command.create({
'product_id': self.product_line_vals_2['product_id'],
'product_uom_id': self.product_line_vals_2['product_uom_id'],
'price_unit': self.product_line_vals_2['price_unit'],
'tax_ids': [Command.set(self.product_line_vals_2['tax_ids'])],
}),
],
})
self.assertInvoiceValues(move, [
@@ -2168,9 +2188,19 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
'currency_id': self.currency_data['currency'].id,
'invoice_payment_term_id': self.pay_terms_a.id,
'invoice_line_ids': [
(0, None, self.product_line_vals_1),
(0, None, self.product_line_vals_2),
]
Command.create({
'product_id': self.product_line_vals_1['product_id'],
'product_uom_id': self.product_line_vals_1['product_uom_id'],
'price_unit': self.product_line_vals_1['price_unit'],
'tax_ids': [Command.set(self.product_line_vals_1['tax_ids'])],
}),
Command.create({
'product_id': self.product_line_vals_2['product_id'],
'product_uom_id': self.product_line_vals_2['product_uom_id'],
'price_unit': self.product_line_vals_2['price_unit'],
'tax_ids': [Command.set(self.product_line_vals_2['tax_ids'])],
}),
],
})
# Remove the invoice_date to check:
@@ -2359,9 +2389,19 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
'currency_id': self.currency_data['currency'].id,
'invoice_payment_term_id': self.pay_terms_a.id,
'invoice_line_ids': [
(0, None, self.product_line_vals_1),
(0, None, self.product_line_vals_2),
]
Command.create({
'product_id': self.product_line_vals_1['product_id'],
'product_uom_id': self.product_line_vals_1['product_uom_id'],
'price_unit': self.product_line_vals_1['price_unit'],
'tax_ids': [Command.set(self.product_line_vals_1['tax_ids'])],
}),
Command.create({
'product_id': self.product_line_vals_2['product_id'],
'product_uom_id': self.product_line_vals_2['product_uom_id'],
'price_unit': self.product_line_vals_2['price_unit'],
'tax_ids': [Command.set(self.product_line_vals_2['tax_ids'])],
}),
],
})
move.action_switch_invoice_into_refund_credit_note()
@@ -2409,10 +2449,6 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
def test_out_invoice_switch_out_refund_2(self):
# Test creating an account_move with an out_invoice_type and switch it in an out_refund and a negative quantity.
modified_product_line_vals_1 = self.product_line_vals_1.copy()
modified_product_line_vals_1.update({'quantity': -modified_product_line_vals_1['quantity']})
modified_product_line_vals_2 = self.product_line_vals_2.copy()
modified_product_line_vals_2.update({'quantity': -modified_product_line_vals_2['quantity']})
move = self.env['account.move'].create({
'move_type': 'out_invoice',
'partner_id': self.partner_a.id,
@@ -2420,9 +2456,21 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
'currency_id': self.currency_data['currency'].id,
'invoice_payment_term_id': self.pay_terms_a.id,
'invoice_line_ids': [
(0, None, modified_product_line_vals_1),
(0, None, modified_product_line_vals_2),
]
Command.create({
'product_id': self.product_line_vals_1['product_id'],
'product_uom_id': self.product_line_vals_1['product_uom_id'],
'price_unit': self.product_line_vals_1['price_unit'],
'quantity': -self.product_line_vals_1['quantity'],
'tax_ids': [Command.set(self.product_line_vals_1['tax_ids'])],
}),
Command.create({
'product_id': self.product_line_vals_2['product_id'],
'product_uom_id': self.product_line_vals_2['product_uom_id'],
'price_unit': self.product_line_vals_2['price_unit'],
'quantity': -self.product_line_vals_2['quantity'],
'tax_ids': [Command.set(self.product_line_vals_2['tax_ids'])],
}),
],
})
self.assertInvoiceValues(move, [
@@ -3,7 +3,7 @@
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.tests.common import Form
from odoo.tests import tagged
from odoo import fields
from odoo import fields, Command
@tagged('post_install', '-at_install')
@@ -827,9 +827,19 @@ class TestAccountMoveOutRefundOnchanges(AccountTestInvoicingCommon):
'currency_id': self.currency_data['currency'].id,
'invoice_payment_term_id': self.pay_terms_a.id,
'invoice_line_ids': [
(0, None, self.product_line_vals_1),
(0, None, self.product_line_vals_2),
]
Command.create({
'product_id': self.product_line_vals_1['product_id'],
'product_uom_id': self.product_line_vals_1['product_uom_id'],
'price_unit': self.product_line_vals_1['price_unit'],
'tax_ids': [Command.set(self.product_line_vals_1['tax_ids'])],
}),
Command.create({
'product_id': self.product_line_vals_2['product_id'],
'product_uom_id': self.product_line_vals_2['product_uom_id'],
'price_unit': self.product_line_vals_2['price_unit'],
'tax_ids': [Command.set(self.product_line_vals_2['tax_ids'])],
}),
],
})
self.assertInvoiceValues(move, [
@@ -877,13 +887,23 @@ class TestAccountMoveOutRefundOnchanges(AccountTestInvoicingCommon):
'currency_id': self.currency_data['currency'].id,
'invoice_payment_term_id': self.pay_terms_a.id,
'invoice_line_ids': [
(0, None, self.product_line_vals_1),
]
Command.create({
'product_id': self.product_line_vals_1['product_id'],
'product_uom_id': self.product_line_vals_1['product_uom_id'],
'price_unit': self.product_line_vals_1['price_unit'],
'tax_ids': [Command.set(self.product_line_vals_1['tax_ids'])],
}),
],
})
move.write({
'invoice_line_ids': [
(0, None, self.product_line_vals_2),
]
Command.create({
'product_id': self.product_line_vals_2['product_id'],
'product_uom_id': self.product_line_vals_2['product_uom_id'],
'price_unit': self.product_line_vals_2['price_unit'],
'tax_ids': [Command.set(self.product_line_vals_2['tax_ids'])],
}),
],
})
self.assertInvoiceValues(move, [
@@ -299,3 +299,31 @@ class TestPurchaseToInvoice(AccountTestInvoicingCommon):
move = move_form.save()
self.assertEqual(move.amount_total, 0.01)
def test_vendor_bill_analytic_account_default_change(self):
""" Tests whether, when an analytic account rule is set, and user changes manually the analytic account on
the po, it is the same that is mentioned in the bill.
"""
analytic_account_default = self.env['account.analytic.account'].create({'name': 'default'})
analytic_account_manual = self.env['account.analytic.account'].create({'name': 'manual'})
self.env['account.analytic.default'].create({
'analytic_id': analytic_account_default.id,
'product_id': self.product_order.id,
})
po_form = Form(self.env['purchase.order'].with_context(tracking_disable=True))
po_form.partner_id = self.partner_a
with po_form.order_line.new() as po_line_form:
po_line_form.name = self.product_order.name
po_line_form.product_id = self.product_order
po_line_form.product_qty = 1.0
po_line_form.price_unit = 10
po_line_form.account_analytic_id = analytic_account_manual
purchase_order = po_form.save()
purchase_order.button_confirm()
purchase_order.action_create_invoice()
aml = self.env['account.move.line'].search([('purchase_line_id', '=', purchase_order.order_line.id)])
self.assertRecordValues(aml, [{'analytic_account_id': analytic_account_manual.id}])