add stock_account_avco_negative_fix

This commit is contained in:
Luis 2026-08-03 13:10:09 +02:00
parent 10a288f8d5
commit b07082f70d
9 changed files with 302 additions and 4 deletions

View file

@ -0,0 +1 @@
from . import test_avco_negative_fix

View file

@ -0,0 +1,108 @@
from odoo.tests import tagged
from odoo.tests.common import TransactionCase
@tagged("post_install", "-at_install")
class TestAvcoNegativeFix(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.supplier_location = cls.env.ref("stock.stock_location_suppliers")
cls.customer_location = cls.env.ref("stock.stock_location_customers")
cls.stock_location = cls.env.ref("stock.stock_location_stock")
cls.picking_type_in = cls.env.ref("stock.picking_type_in")
cls.picking_type_out = cls.env.ref("stock.picking_type_out")
cls.categ_avco = cls.env["product.category"].create(
{"name": "Test AVCO category", "property_cost_method": "average"}
)
cls.categ_fifo = cls.env["product.category"].create(
{"name": "Test FIFO category", "property_cost_method": "fifo"}
)
def _create_product(self, categ, standard_price):
return self.env["product.product"].create(
{
"name": "Test product %s" % categ.name,
"type": "product",
"categ_id": categ.id,
"standard_price": standard_price,
}
)
def _validate_move(self, product, qty, location_id, location_dest_id, price_unit=None):
move_vals = {
"name": "test move",
"product_id": product.id,
"product_uom_qty": qty,
"product_uom": product.uom_id.id,
"location_id": location_id.id,
"location_dest_id": location_dest_id.id,
}
if price_unit is not None:
move_vals["price_unit"] = price_unit
move = self.env["stock.move"].create(move_vals)
move._action_confirm()
move.quantity_done = qty
move._action_done()
return move
def _sell_without_stock(self, product, qty):
return self._validate_move(
product, qty, self.stock_location, self.customer_location
)
def _receive(self, product, qty, price_unit):
return self._validate_move(
product, qty, self.supplier_location, self.stock_location, price_unit
)
def test_negative_result_is_corrected(self):
"""TC-01 (RF-01): incoming move on a product with quantity_svl <= 0
(risk precondition) unconditionally gets standard_price set to the
incoming move's price unit, reproducing product_id=8611."""
product = self._create_product(self.categ_avco, 6.71)
self._sell_without_stock(product, 1.056)
self.assertAlmostEqual(product.quantity_svl, -1.056, places=3)
self.assertAlmostEqual(product.standard_price, 6.71, places=2)
self._receive(product, 1.0, 7.56)
self.assertGreater(product.standard_price, 0.0)
self.assertAlmostEqual(product.standard_price, 7.56, places=2)
def test_zero_result_is_corrected(self):
"""TC-02 (RF-02): quantity_svl exactly 0 before the incoming move is
also a risk precondition; standard_price is set to the incoming
move's price unit."""
product = self._create_product(self.categ_avco, 6.0)
self._sell_without_stock(product, 2.0)
self.assertAlmostEqual(product.quantity_svl, -2.0, places=3)
self.assertAlmostEqual(product.standard_price, 6.0, places=2)
self._receive(product, 3.0, 4.0)
self.assertNotAlmostEqual(product.standard_price, 0.0, places=2)
self.assertAlmostEqual(product.standard_price, 4.0, places=2)
def test_positive_result_is_untouched(self):
"""TC-03 (RF-03): with quantity_svl > 0 before the incoming move
(no risk precondition), the post-processing fix does not
intervene at all and the core's weighted average is left as-is."""
product = self._create_product(self.categ_avco, 1.0)
self._receive(product, 10.0, 10.0)
self.assertAlmostEqual(product.quantity_svl, 10.0, places=3)
self.assertAlmostEqual(product.standard_price, 10.0, places=2)
self._receive(product, 10.0, 20.0)
self.assertAlmostEqual(product.standard_price, 15.0, places=2)
def test_non_avco_product_not_affected(self):
"""TC-04 (RF-04): the fix only applies to cost_method == 'average'
products; FIFO products follow the core's unmodified behaviour."""
product = self._create_product(self.categ_fifo, 5.0)
self.assertEqual(product.cost_method, "fifo")
self._receive(product, 10.0, 8.0)
self.assertAlmostEqual(product.standard_price, 8.0, places=2)