From eaae8d1ba4909042e276065d106c47e9117b2a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=80ngel=20=C3=80lvarez?= Date: Thu, 8 Apr 2021 10:40:42 +0200 Subject: [PATCH] product_cost_fifo: Use all moves to compute FIFO We must consider any incoming moves (ex: inventory) for the computation of the FIFO otherwise the back computation to find first in moves does not pick enough moves. As not all incoming moves have a unit price, we use the current cost price as fallback. Also in re-computation, the in or out move test should only rely on the storage location usage in order to properly compute the average cost. issue9274 product_cost_fifo: Keep last cost price when quantity is zero When there is no quantity in stock, we keep the last cost price instead of setting it to zero. This gives the same behavior between on move computation and on product recomputation. issue9443 --- product.py | 41 +++++++++++-------- ...product_cost_fifo_recompute_cost_price.rst | 23 +++++++---- 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/product.py b/product.py index 0c204bd..b7e25bb 100644 --- a/trytond/trytond/modules/product_cost_fifo/product.py +++ b/trytond/trytond/modules/product_cost_fifo/product.py @@ -30,7 +30,7 @@ class Product(metaclass=PoolMeta): domain = [ ('product', '=', self.id), self._domain_moves_cost(), - ('from_location.type', 'in', ['supplier', 'production']), + ('from_location.type', '!=', 'storage'), ('to_location.type', '=', 'storage'), ] if not date: @@ -134,11 +134,10 @@ class Product(metaclass=PoolMeta): quantity = Decimal(str(quantity)) def in_move(move): - return (move.from_location.type in ['supplier', 'production'] - or move.to_location.type == 'supplier') + return move.to_location.type == 'storage' def out_move(move): - return not in_move(move) + return move.from_location.type == 'storage' def compute_fifo_cost_price(quantity, date): fifo_moves = self.get_fifo_move( @@ -149,12 +148,15 @@ class Product(metaclass=PoolMeta): consumed_qty = 0 for move, move_qty in fifo_moves: consumed_qty += move_qty - with Transaction().set_context(date=move.effective_date): - unit_price = Currency.compute( - move.currency, move.unit_price, - move.company.currency, round=False) - unit_price = Uom.compute_price( - move.uom, unit_price, move.product.default_uom) + if move.from_location.type in {'supplier', 'production'}: + with Transaction().set_context(date=move.effective_date): + unit_price = Currency.compute( + move.currency, move.unit_price, + move.company.currency, round=False) + unit_price = Uom.compute_price( + move.uom, unit_price, move.product.default_uom) + else: + unit_price = move.cost_price or 0 cost_price += unit_price * Decimal(str(move_qty)) if consumed_qty: return (cost_price / Decimal(str(consumed_qty))).quantize( @@ -189,7 +191,7 @@ class Product(metaclass=PoolMeta): - (fifo_cost_price * current_out_qty)) / quantity) else: - cost_price = Decimal(0) + cost_price = current_cost_price current_cost_price = cost_price.quantize( Decimal(str(10.0 ** -digits[1]))) current_moves.clear() @@ -201,12 +203,15 @@ class Product(metaclass=PoolMeta): if move.from_location.type == 'storage': qty *= -1 if in_move(move): - with Transaction().set_context(date=move.effective_date): - unit_price = Currency.compute( - move.currency, move.unit_price, - move.company.currency, round=False) - unit_price = Uom.compute_price( - move.uom, unit_price, self.default_uom) + if move.from_location.type in {'supplier', 'production'}: + with Transaction().set_context(date=move.effective_date): + unit_price = Currency.compute( + move.currency, move.unit_price, + move.company.currency, round=False) + unit_price = Uom.compute_price( + move.uom, unit_price, self.default_uom) + else: + unit_price = cost_price if quantity + qty > 0 and quantity >= 0: cost_price = ( (cost_price * quantity) + (unit_price * qty) @@ -215,7 +220,7 @@ class Product(metaclass=PoolMeta): cost_price = unit_price current_cost_price = cost_price.quantize( Decimal(str(10.0 ** -digits[1]))) - else: + elif out_move(move): current_out_qty += -qty quantity += qty diff --git a/tests/scenario_product_cost_fifo_recompute_cost_price.rst b/tests/scenario_product_cost_fifo_recompute_cost_price.rst index f8b952f..ff76174 100644 --- a/trytond/trytond/modules/product_cost_fifo/tests/scenario_product_cost_fifo_recompute_cost_price.rst +++ b/trytond/trytond/modules/product_cost_fifo/tests/scenario_product_cost_fifo_recompute_cost_price.rst @@ -45,6 +45,7 @@ Get stock locations:: >>> supplier_loc, = Location.find([('code', '=', 'SUP')]) >>> storage_loc, = Location.find([('code', '=', 'STO')]) >>> customer_loc, = Location.find([('code', '=', 'CUS')]) + >>> lost_found, = Location.find([('name', '=', "Lost and Found")]) Create some moves:: @@ -65,6 +66,12 @@ Create some moves:: ... effective_date=today - dt.timedelta(days=1)).click('do') >>> StockMove( ... product=product, + ... quantity=1, + ... from_location=lost_found, + ... to_location=storage_loc, + ... effective_date=today - dt.timedelta(days=1)).click('do') + >>> StockMove( + ... product=product, ... quantity=2, ... from_location=storage_loc, ... to_location=customer_loc, @@ -88,17 +95,16 @@ Create some moves:: ... product=product, ... quantity=1, ... from_location=storage_loc, - ... to_location=customer_loc, - ... unit_price=Decimal('300'), + ... to_location=lost_found, ... effective_date=today).click('do') >>> [m.cost_price for m in StockMove.find([])] - [Decimal('100.0000'), Decimal('110.0000'), Decimal('105.0000'), Decimal('110.0000'), Decimal('113.3333'), Decimal('100.0000')] + [Decimal('100.0000'), Decimal('116.6666'), Decimal('106.6666'), Decimal('110.0000'), Decimal('113.3333'), Decimal('113.3333'), Decimal('100.0000')] >>> product.reload() >>> product.cost_price - Decimal('100.0000') + Decimal('99.9998') Recompute cost price:: @@ -106,11 +112,12 @@ Recompute cost price:: >>> recompute.execute('recompute') >>> [m.cost_price for m in StockMove.find([])] - [Decimal('106.6667'), Decimal('106.6667'), Decimal('105.0000'), Decimal('110.0000'), Decimal('113.3333'), Decimal('100.0000')] + [Decimal('111.1111'), Decimal('111.1111'), Decimal('106.6666'), Decimal('110.0000'), Decimal('113.3333'), Decimal('113.3333'), Decimal('100.0000')] >>> product.reload() >>> product.cost_price - Decimal('99.9999') + Decimal('99.9998') + Recompute cost price from a date:: @@ -119,8 +126,8 @@ Recompute cost price from a date:: >>> recompute.execute('recompute') >>> [m.cost_price for m in StockMove.find([])] - [Decimal('106.6667'), Decimal('106.6667'), Decimal('105.0000'), Decimal('110.0000'), Decimal('113.3333'), Decimal('100.0000')] + [Decimal('111.1111'), Decimal('111.1111'), Decimal('106.6666'), Decimal('110.0000'), Decimal('113.3333'), Decimal('113.3333'), Decimal('100.0000')] >>> product.reload() >>> product.cost_price - Decimal('99.9999') + Decimal('99.9998') -- 2.25.1