changeset b97f12a119f2 in modules/product_cost_fifo:default
details: 
https://hg.tryton.org/modules/product_cost_fifo?cmd=changeset&node=b97f12a119f2
description:
        Exclude quantity coming back from production the same day

        This avoid to have a loop back that keeps the initial cost price of the
        production in the history.

        issue11265
        review386101002
diffstat:

 product.py                                                           |   29 ++-
 tests/scenario_product_cost_fifo_recompute_cost_price_production.rst |  105 
++++++++++
 tests/test_product_cost_fifo.py                                      |    5 +
 3 files changed, 133 insertions(+), 6 deletions(-)

diffs (203 lines):

diff -r 356d29f8a922 -r b97f12a119f2 product.py
--- a/product.py        Sat Dec 18 16:31:09 2021 +0100
+++ b/product.py        Sun Mar 06 13:34:18 2022 +0100
@@ -139,6 +139,11 @@
         def out_move(move):
             return not in_move(move)
 
+        def production_move(move):
+            return (
+                move.from_location.type == 'production'
+                or move.to_location.type == 'production')
+
         def compute_fifo_cost_price(quantity, date):
             fifo_moves = self.get_fifo_move(float(quantity), date=date)
 
@@ -154,10 +159,13 @@
         # in order to keep quantity positive where possible
         # We do not re-browse because we expect only small changes
         moves = sorted(moves, key=lambda m: (
-                m.effective_date, out_move(m), m.id))
+                m.effective_date,
+                out_move(m) or (in_move(m) and production_move(m)),
+                m.id))
         current_moves = []
         current_out_qty = 0
         current_cost_price = cost_price
+        qty_production = 0
         for move in moves:
             if (current_moves
                     and current_moves[-1].effective_date
@@ -188,6 +196,7 @@
                     current_cost_price = round_price(cost_price)
                 current_moves.clear()
                 current_out_qty = 0
+                qty_production = 0
             current_moves.append(move)
 
             cost_price = Revision.apply_up_to(
@@ -197,17 +206,23 @@
             if out_move(move):
                 qty *= -1
             if in_move(move):
+                in_qty = qty
+                if production_move(move) and qty_production < 0:
+                    # Exclude quantity coming back from production
+                    in_qty -= min(abs(qty_production), in_qty)
                 unit_price = move.get_cost_price(product_cost_price=cost_price)
-                if quantity + qty > 0 and quantity >= 0:
+                if quantity + in_qty > 0 and quantity >= 0:
                     cost_price = (
-                        (cost_price * quantity) + (unit_price * qty)
-                        ) / (quantity + qty)
-                elif qty > 0:
+                        (cost_price * quantity) + (unit_price * in_qty)
+                        ) / (quantity + in_qty)
+                elif in_qty > 0:
                     cost_price = unit_price
                 current_cost_price = round_price(cost_price)
             elif out_move(move):
                 current_out_qty += -qty
             quantity += qty
+            if production_move(move):
+                qty_production += qty
 
         Move.write([
                 m for m in filter(in_move, current_moves)
@@ -224,11 +239,13 @@
                     m for m in out_moves
                     if m.cost_price != fifo_cost_price],
                 dict(cost_price=fifo_cost_price))
-            if quantity:
+            if quantity > 0:
                 cost_price = (
                     ((cost_price * (quantity + current_out_qty))
                         - (fifo_cost_price * current_out_qty))
                     / quantity)
+            else:
+                cost_price = current_cost_price
         for revision in revisions:
             cost_price = revision.get_cost_price(cost_price)
         return cost_price
diff -r 356d29f8a922 -r b97f12a119f2 
tests/scenario_product_cost_fifo_recompute_cost_price_production.rst
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/scenario_product_cost_fifo_recompute_cost_price_production.rst      
Sun Mar 06 13:34:18 2022 +0100
@@ -0,0 +1,105 @@
+=================================================
+Product Cost FIFO Recompute Cost Price Production
+=================================================
+
+Imports::
+
+    >>> import datetime as dt
+    >>> from decimal import Decimal
+
+    >>> from proteus import Model, Wizard
+    >>> from trytond.tests.tools import activate_modules
+    >>> from trytond.modules.company.tests.tools import (
+    ...     create_company, get_company)
+
+    >>> today = dt.date.today()
+    >>> yesterday = today - dt.timedelta(days=1)
+
+Activate modules::
+
+    >>> config = activate_modules('product_cost_fifo')
+
+    >>> Location = Model.get('stock.location')
+    >>> ProductUom = Model.get('product.uom')
+    >>> ProductTemplate = Model.get('product.template')
+    >>> StockMove = Model.get('stock.move')
+
+Create company::
+
+    >>> _ = create_company()
+    >>> company = get_company()
+
+Create product::
+
+    >>> unit, = ProductUom.find([('name', '=', 'Unit')])
+
+    >>> template = ProductTemplate()
+    >>> template.name = 'Product'
+    >>> template.default_uom = unit
+    >>> template.type = 'goods'
+    >>> template.list_price = Decimal('50.0000')
+    >>> template.cost_price_method = 'fifo'
+    >>> product, = template.products
+    >>> product.cost_price = Decimal('40.0000')
+    >>> template.save()
+    >>> product, = template.products
+
+Get stock locations::
+
+    >>> supplier_loc, = Location.find([('code', '=', 'SUP')])
+    >>> storage_loc, = Location.find([('code', '=', 'STO')])
+    >>> production_loc = Location(name="Production", type='production')
+    >>> production_loc.save()
+
+Consume product for production and reverse some::
+
+    >>> StockMove(
+    ...     product=product,
+    ...     quantity=10,
+    ...     from_location=storage_loc,
+    ...     to_location=production_loc,
+    ...     effective_date=today).click('do')
+    >>> StockMove(
+    ...     product=product,
+    ...     quantity=2,
+    ...     from_location=production_loc,
+    ...     to_location=storage_loc,
+    ...     unit_price=Decimal('40.0000'),
+    ...     effective_date=today).click('do')
+
+    >>> [m.cost_price for m in StockMove.find([])]
+    [Decimal('40.0000'), Decimal('40.0000')]
+
+Recompute cost price::
+
+    >>> recompute = Wizard('product.recompute_cost_price', [product])
+    >>> recompute.execute('recompute')
+
+    >>> [m.cost_price for m in StockMove.find([])]
+    [Decimal('0.0000'), Decimal('40.0000')]
+
+    >>> product.reload()
+    >>> product.cost_price
+    Decimal('0.0000')
+
+Receive product yesterday at new cost::
+
+    >>> StockMove(
+    ...     product=product,
+    ...     quantity=16,
+    ...     from_location=supplier_loc,
+    ...     to_location=storage_loc,
+    ...     unit_price=Decimal('20.0000'),
+    ...     effective_date=yesterday).click('do')
+
+Recompute cost price::
+
+    >>> recompute = Wizard('product.recompute_cost_price', [product])
+    >>> recompute.execute('recompute')
+
+    >>> [m.cost_price for m in StockMove.find([])]
+    [Decimal('20.0000'), Decimal('20.0000'), Decimal('20.0000')]
+
+    >>> product.reload()
+    >>> product.cost_price
+    Decimal('20.0000')
diff -r 356d29f8a922 -r b97f12a119f2 tests/test_product_cost_fifo.py
--- a/tests/test_product_cost_fifo.py   Sat Dec 18 16:31:09 2021 +0100
+++ b/tests/test_product_cost_fifo.py   Sun Mar 06 13:34:18 2022 +0100
@@ -37,4 +37,9 @@
             tearDown=doctest_teardown, encoding='utf-8',
             checker=doctest_checker,
             optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
+    suite.addTests(doctest.DocFileSuite(
+            'scenario_product_cost_fifo_recompute_cost_price_production.rst',
+            tearDown=doctest_teardown, encoding='utf-8',
+            checker=doctest_checker,
+            optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
     return suite

Reply via email to