[FIX] purchase_stock,stock_account: add analytic distribution to valuation lines

Steps to reproduce:
- Using a company whose currency is USD, make sure to have a rate change with EUR a week before Today.
- Create an analytic plan with Default Applicability set to Mandatory.
- Create an analytic account for the plan.
- Create a Storable Product with a Product Category that has FIFO Costing Method and Automated Inventory Valuation.
- Create a Request for Quotation in EUR using the new product and the new analytic account.
- Confirm it, click on Receive Products, then Validate.
- Go back to the RFQ and click on Create Bill.
- Change the date to two weeks ago (in order to trigger an exchange difference).
- Confirm it.

Current Behavior:
A `ValidationError` is raised in `_validate_distribution()`: "One or more lines require a 100% analytic distribution."

Cause:
This happens because the exchange difference journal items are created without `analytic_distribution` even though they should, since it was set as mandatory.

Solution:
Make sure the `analytic_distribution` is properly set when creating stock valuation lines.

A test is also added to cover this use case.

task-3582164

closes odoo/odoo#147387

X-original-commit: fafcf452c68aad27754a8c9bd7bde1742be61cb8
Signed-off-by: Habib Ayob (ayh) <ayh@odoo.com>
Signed-off-by: John Laterre (jol) <jol@odoo.com>
This commit is contained in:
John Laterre (jol)
2024-01-10 10:28:09 +00:00
parent 344e3262d8
commit 88f53b7315
2 changed files with 108 additions and 13 deletions
@@ -1,6 +1,9 @@
from odoo import Command, fields
from odoo.tests import tagged
from .common import PurchaseTestCommon
@tagged('post_install', '-at_install')
class TestPurchaseOrderProcess(PurchaseTestCommon):
def test_00_cancel_purchase_order_flow(self):
@@ -54,3 +57,91 @@ class TestPurchaseOrderProcess(PurchaseTestCommon):
})
po.button_confirm()
self.assertEqual(po.order_line.move_ids.product_packaging_id, packaging)
def test_analytic_distribution_propagation_with_exchange_difference(self):
# Create 2 rates in order to generate an exchange difference later.
eur = self.env.ref('base.EUR')
eur.write({
'rate_ids': [
Command.clear(),
Command.create({
'name': fields.Date.from_string('2023-01-01'),
'company_rate': 2.0,
}),
Command.create({
'name': fields.Date.from_string('2023-12-01'),
'company_rate': 3.0,
}),
],
'active': True,
})
# Create a mandatory analytic account.
analytic_plan = self.env['account.analytic.plan'].create({
'name': 'Analytic Plan',
'default_applicability': 'mandatory',
})
analytic_account = self.env['account.analytic.account'].create({
'name': 'Analytic Account',
'plan_id': analytic_plan.id},
)
# Create a storable product with FIFO costing method and automated inventory valuation.
analytic_product_category = self.env['product.category'].create({
'name': 'Analytic Product Category',
'property_cost_method': 'fifo',
'property_valuation': 'real_time',
})
analytic_product = self.env['product.product'].create({
'name': 'Analytic Product',
'detailed_type': 'product',
'categ_id': analytic_product_category.id,
'lst_price': 100.0,
'standard_price': 25.0,
})
# Create and confirm a Purchase Order using aforementioned product and currency.
purchase_order = self.env['purchase.order'].create({
'date_order': fields.Date.from_string('2023-12-04'),
'currency_id': eur.id,
'partner_id': self.partner.id,
'order_line': [
Command.create({
'product_id': analytic_product.id,
'product_qty': 10.0,
'analytic_distribution': {analytic_account.id: 100},
}),
],
})
purchase_order.button_confirm()
# Make sure a stock move has been created to replenish the product.
self.assertEqual(len(purchase_order.picking_ids.move_ids), 1)
stock_move = purchase_order.picking_ids.move_ids
stock_move.quantity = stock_move.product_uom_qty
purchase_order.picking_ids.button_validate()
purchase_order.action_create_invoice()
# Make sure a first Journal Entry has been created (to account for the stock move).
self.assertEqual(len(stock_move.account_move_ids), 1)
stock_account_move = stock_move.account_move_ids
# Make sure the Vendor Bill has been created,
# and confirm it at an earlier date (to generate the exchange difference).
self.assertEqual(len(purchase_order.invoice_ids), 1)
vendor_bill = purchase_order.invoice_ids
vendor_bill.invoice_date = fields.Date.from_string('2023-11-01')
vendor_bill.action_post()
# Make sure a second Journal Entry has been created (to account for the exchange difference).
self.assertEqual(len(stock_move.account_move_ids), 2)
exchange_account_move = stock_move.account_move_ids - stock_account_move
# Make sure both exchange Journal Items have the correct analytic distribution.
self.assertEqual(len(exchange_account_move.line_ids), 2)
for line in exchange_account_move.line_ids:
self.assertTrue(line.analytic_distribution)
self.assertEqual(line.analytic_distribution[str(analytic_account.id)], 100)
+17 -13
View File
@@ -463,29 +463,33 @@ class StockMove(models.Model):
def _generate_valuation_lines_data(self, partner_id, qty, debit_value, credit_value, debit_account_id, credit_account_id, svl_id, description):
# This method returns a dictionary to provide an easy extension hook to modify the valuation lines (see purchase for an example)
self.ensure_one()
debit_line_vals = {
line_vals = {
'name': description,
'product_id': self.product_id.id,
'quantity': qty,
'product_uom_id': self.product_id.uom_id.id,
'ref': description,
'partner_id': partner_id,
'balance': debit_value,
'account_id': debit_account_id,
}
credit_line_vals = {
'name': description,
'product_id': self.product_id.id,
'quantity': qty,
'product_uom_id': self.product_id.uom_id.id,
'ref': description,
'partner_id': partner_id,
'balance': -credit_value,
'account_id': credit_account_id,
svl = self.env['stock.valuation.layer'].browse(svl_id)
if svl.account_move_line_id.analytic_distribution:
line_vals['analytic_distribution'] = svl.account_move_line_id.analytic_distribution
rslt = {
'credit_line_vals': {
**line_vals,
'balance': -credit_value,
'account_id': credit_account_id,
},
'debit_line_vals': {
**line_vals,
'balance': debit_value,
'account_id': debit_account_id,
},
}
rslt = {'credit_line_vals': credit_line_vals, 'debit_line_vals': debit_line_vals}
if credit_value != debit_value:
# for supplier returns of product in average costing method, in anglo saxon mode
diff_amount = debit_value - credit_value